Files
cmake/Source/cmSystemTools.cxx
T
Tyler Yankee 4397f89c16 Restore archive extraction between directory symlinks
Commit 03f19aa4ea (cmSystemTools: Fix path traversal vulnerability in
archive extraction, 2026-01-06, v4.3.0-rc1~179^2) incidentally
introduced the `ARCHIVE_EXTRACT_SECURE_SYMLINKS` flag when extracting
archives, which prevents extraction of objects whose location would be
altered by a symlink on disk. However, this particular behavior change
was never documented or tested alongside the other changes in the
commit (absolute paths and path traversal components).

Divergent behavior of this flag from `libarchive` on Windows and
non-Windows platforms needs further investigation. In particular, on
Windows, archives cannot be extracted through directory symlinks, but
can be extracted through junctions. On Linux, archives *can* still be
extracted through symlinks, and archives containing symlinks can be
extracted as well.

The symlink change breaks compatibility with many common use cases of
existing projects, e.g., when extracting an archive inside a symlinked
build directory. Revert the flag until this issue can be explored in a
future version of CMake.

Fixes: #27752
Reported-by: scivision <scivision@users.noreply.github.com>
2026-04-14 15:57:13 -04:00

4397 lines
126 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst 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(__DragonFly__) || defined(__NetBSD__) || \
defined(__QNX__)
// For isascii
// NOLINTNEXTLINE(bugprone-reserved-identifier)
# define _XOPEN_SOURCE 700
#endif
#if defined(__APPLE__)
// Restore Darwin APIs removed by _POSIX_C_SOURCE.
// NOLINTNEXTLINE(bugprone-reserved-identifier)
# define _DARWIN_C_SOURCE
#endif
#ifndef __has_feature
# define __has_feature(x) 0
#endif
#if !defined(__clang__) || __has_feature(cxx_thread_local)
# define CM_HAVE_THREAD_LOCAL
#endif
#include "cmSystemTools.h"
#include <iterator>
#if defined(_WIN32) || defined(__APPLE__)
# include <unordered_map>
#endif
#include <cm/optional>
#include <cmext/algorithm>
#include <cmext/string_view>
#include <cm3p/uv.h>
#include "cmDuration.h"
#include "cmELF.h"
#include "cmMessageMetadata.h"
#include "cmPathResolver.h"
#include "cmProcessOutput.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmUVHandlePtr.h"
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
#include "cmValue.h"
#include "cmWorkingDirectory.h"
#if !defined(CMAKE_BOOTSTRAP)
# include <cm3p/archive.h>
# include <cm3p/archive_entry.h>
# include "cmArchiveWrite.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_USE_MACH_PARSER)
# include "cmMachO.h"
#endif
#if defined(CMake_USE_XCOFF_PARSER)
# include "cmXCOFF.h"
#endif
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <random>
#include <sstream>
#include <utility>
#include <vector>
#ifndef CM_HAVE_THREAD_LOCAL
# include <mutex>
#endif
#include <fcntl.h>
#include "cmsys/Directory.hxx"
#ifdef _WIN32
# include "cmsys/Encoding.hxx"
#endif
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/String.h"
#include "cmsys/System.h"
#if defined(_WIN32)
# include <windows.h>
# include <knownfolders.h>
# include <shlobj.h>
// include wincrypt.h after windows.h
# include <wincrypt.h>
#else
# include <unistd.h>
# include <sys/time.h>
#endif
#if defined(_WIN32) && \
(defined(_MSC_VER) || defined(__WATCOMC__) || defined(__MINGW32__))
# include <io.h>
#endif
#if defined(__APPLE__)
# include <mach-o/dyld.h>
#endif
#ifdef __QNX__
# include <malloc.h> /* for malloc/free on QNX */
#endif
#ifdef __linux__
# include <linux/fs.h>
# include <sys/ioctl.h>
#endif
#if !defined(_WIN32) && !defined(__ANDROID__)
# include <sys/utsname.h>
#endif
#if defined(CMAKE_BOOTSTRAP) && defined(__sun) && defined(__i386)
# define CMAKE_NO_MKDTEMP
#endif
#ifdef CMAKE_NO_MKDTEMP
# include <dlfcn.h>
#endif
#ifndef CMAKE_NO_GETPWNAM
# if defined(_WIN32)
# define CMAKE_NO_GETPWNAM
# endif
#endif
#ifndef CMAKE_NO_GETPWNAM
# include <pwd.h>
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1800
# define CM_WINDOWS_DEPRECATED_GetVersionEx
#endif
namespace {
cmSystemTools::InterruptCallback s_InterruptCallback;
cmSystemTools::MessageCallback s_MessageCallback;
cmSystemTools::OutputCallback s_StderrCallback;
cmSystemTools::OutputCallback s_StdoutCallback;
std::string ResolveTildePath(std::string p)
{
if (!p.empty() && p[0] == '~') {
cm::optional<std::string> home;
std::string::size_type last = p.find_first_of("/\\");
if (last == std::string::npos) {
last = p.size();
}
if (last == 1) {
#if defined(_WIN32) && !defined(__CYGWIN__)
home = cmSystemTools::GetEnvVar("USERPROFILE");
if (!home)
#endif
home = cmSystemTools::GetEnvVar("HOME");
#ifndef CMAKE_NO_GETPWNAM
} else if (last > 1) {
std::string user = p.substr(1, last - 1);
if (passwd* pw = getpwnam(user.c_str())) {
home = std::string(pw->pw_dir);
}
#endif
}
if (home) {
p.replace(0, last, *home);
}
}
return p;
}
#ifdef _WIN32
std::string GetDosDriveWorkingDirectory(char letter)
{
// The Windows command processor tracks a per-drive working
// directory for compatibility with MS-DOS by using special
// environment variables named "=C:".
// https://web.archive.org/web/20100522040616/
// https://blogs.msdn.com/oldnewthing/archive/2010/05/06/10008132.aspx
return cmSystemTools::GetEnvVar(cmStrCat('=', letter, ':'))
.value_or(std::string());
}
cmsys::Status ReadNameOnDisk(std::string const& path, std::string& name)
{
std::wstring wp = cmsys::Encoding::ToWide(path);
HANDLE h = CreateFileW(
wp.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (h == INVALID_HANDLE_VALUE) {
return cmsys::Status::Windows_GetLastError();
}
WCHAR local_fni[((sizeof(FILE_NAME_INFO) - 1) / sizeof(WCHAR)) + 1024];
size_t fni_size = sizeof(local_fni);
auto* fni = reinterpret_cast<FILE_NAME_INFO*>(local_fni);
if (!GetFileInformationByHandleEx(h, FileNameInfo, fni, fni_size)) {
DWORD e = GetLastError();
if (e != ERROR_MORE_DATA) {
CloseHandle(h);
return cmsys::Status::Windows(e);
}
fni_size = fni->FileNameLength;
fni = static_cast<FILE_NAME_INFO*>(malloc(fni_size));
if (!fni) {
e = ERROR_NOT_ENOUGH_MEMORY;
CloseHandle(h);
return cmsys::Status::Windows(e);
}
if (!GetFileInformationByHandleEx(h, FileNameInfo, fni, fni_size)) {
e = GetLastError();
free(fni);
CloseHandle(h);
return cmsys::Status::Windows(e);
}
}
std::wstring wn{ fni->FileName, fni->FileNameLength / sizeof(WCHAR) };
std::string nn = cmsys::Encoding::ToNarrow(wn);
std::string::size_type last_slash = nn.find_last_of("/\\");
if (last_slash != std::string::npos) {
name = nn.substr(last_slash + 1);
}
if (fni != reinterpret_cast<FILE_NAME_INFO*>(local_fni)) {
free(fni);
}
CloseHandle(h);
return cmsys::Status::Success();
}
#elif defined(__APPLE__)
cmsys::Status ReadNameOnDiskIterateDir(std::string const& path,
std::string& name)
{
// Read contents of the parent directory to find the
// entry matching the given path.
std::string const bn = cmSystemTools::GetFilenameName(path);
std::string const dn = cmSystemTools::GetFilenamePath(path);
DIR* d = opendir(dn.c_str());
while (struct dirent* dr = readdir(d)) {
if (strcasecmp(dr->d_name, bn.c_str()) == 0) {
name = dr->d_name;
closedir(d);
return cmsys::Status::Success();
}
}
closedir(d);
return cmsys::Status::POSIX(ENOENT);
}
cmsys::Status ReadNameOnDiskFcntlGetPath(std::string const& path,
std::string& name)
{
// macOS (and *BSD) offer a syscall to get an on-disk path to
// a descriptor's file.
int fd = open(path.c_str(), O_SYMLINK | O_RDONLY);
if (fd == -1) {
return cmsys::Status::POSIX(errno);
}
char out[MAXPATHLEN + 1];
if (fcntl(fd, F_GETPATH, out) == -1) {
int e = errno;
close(fd);
return cmsys::Status::POSIX(e);
}
close(fd);
name = cmSystemTools::GetFilenameName(out);
return cmsys::Status::Success();
}
cmsys::Status ReadNameOnDisk(std::string const& path, std::string& name)
{
struct stat stat_path;
if (lstat(path.c_str(), &stat_path) != 0) {
return cmsys::Status::POSIX(errno);
}
// macOS (and *BSD) use namei(9) to cache file paths. Use it unless
// the inode has multiple hardlinks: if it is opened through multiple
// paths, the results may be unpredictable.
if (S_ISDIR(stat_path.st_mode) || stat_path.st_nlink < 2) {
return ReadNameOnDiskFcntlGetPath(path, name);
}
// Fall back to reading the parent directory.
return ReadNameOnDiskIterateDir(path, name);
}
#endif
class RealSystem : public cm::PathResolver::System
{
public:
~RealSystem() override = default;
cmsys::Status ReadSymlink(std::string const& path,
std::string& link) override
{
return cmSystemTools::ReadSymlink(path, link);
}
bool PathExists(std::string const& path) override
{
return cmSystemTools::PathExists(path);
}
std::string GetWorkingDirectory() override
{
return cmSystemTools::GetLogicalWorkingDirectory();
}
#ifdef _WIN32
std::string GetWorkingDirectoryOnDrive(char letter) override
{
return GetDosDriveWorkingDirectory(letter);
}
#endif
#if defined(_WIN32) || defined(__APPLE__)
struct NameOnDisk
{
cmsys::Status Status;
std::string Name;
};
using NameOnDiskMap = std::unordered_map<std::string, NameOnDisk>;
NameOnDiskMap CachedNameOnDisk;
cmsys::Status ReadName(std::string const& path, std::string& name) override
{
// Cache results to avoid repeated filesystem access.
// We assume any files created by our own process keep their case.
// Index the cache by lower-case paths to make it case-insensitive.
std::string path_lower = cmSystemTools::LowerCase(path);
auto i = this->CachedNameOnDisk.find(path_lower);
if (i == this->CachedNameOnDisk.end()) {
i = this->CachedNameOnDisk.emplace(path_lower, NameOnDisk()).first;
i->second.Status = ReadNameOnDisk(path, i->second.Name);
}
name = i->second.Name;
return i->second.Status;
}
#endif
};
RealSystem RealOS;
} // namespace
#if !defined(HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE)
// For GetEnvironmentVariables
# if defined(_WIN32)
extern __declspec(dllimport) char** environ;
# else
extern char** environ; // NOLINT(readability-redundant-declaration)
# endif
#endif
#if !defined(CMAKE_BOOTSTRAP)
// Get path that was read from the archive.
static char const* cm_archive_entry_pathname(struct archive_entry* entry)
{
# ifdef _WIN32
// libarchive converts the archive's encoding to our UTF-8 encoding.
return archive_entry_pathname_utf8(entry);
# else
// libarchive converts the archive's encoding to our locale's encoding.
return archive_entry_pathname(entry);
# endif
}
// Open archive file for reading.
static int cm_archive_read_open_filename(struct archive* a, char const* file,
int block_size)
{
# ifdef _WIN32
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 <crt_externs.h>
# define environ (*_NSGetEnviron())
#endif
bool cmSystemTools::s_RunCommandHideConsole = false;
bool cmSystemTools::s_DisableRunCommandOutput = false;
bool cmSystemTools::s_ErrorOccurred = false;
bool cmSystemTools::s_FatalErrorOccurred = 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(std::string const& m)
{
std::string message = "CMake Error: " + m;
cmSystemTools::s_ErrorOccurred = 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(std::string const& s)
{
if (s_StderrCallback) {
s_StderrCallback(s);
} else {
std::cerr << s << std::flush;
}
}
void cmSystemTools::Stdout(std::string const& s)
{
if (s_StdoutCallback) {
s_StdoutCallback(s);
} else {
std::cout << s << std::flush;
}
}
void cmSystemTools::Message(std::string const& m, char const* title)
{
cmMessageMetadata md;
md.title = title;
Message(m, md);
}
void cmSystemTools::Message(std::string const& m, cmMessageMetadata const& md)
{
if (s_MessageCallback) {
s_MessageCallback(m, md);
} else {
std::cerr << m << std::endl;
}
}
void cmSystemTools::ReportLastSystemError(char const* msg)
{
std::string m =
cmStrCat(msg, ": System Error: ", Superclass::GetLastSystemError());
cmSystemTools::Error(m);
}
void cmSystemTools::ParseWindowsCommandLine(char const* command,
std::vector<std::string>& 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 (char const* 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 (cmsysString_isspace(*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(cmSystemToolsArgV const&) = delete;
cmSystemToolsArgV& operator=(cmSystemToolsArgV const&) = delete;
void Store(std::vector<std::string>& args) const
{
for (char** arg = this->ArgV; arg && *arg; ++arg) {
args.emplace_back(*arg);
}
}
};
void cmSystemTools::ParseUnixCommandLine(char const* command,
std::vector<std::string>& args)
{
// Invoke the underlying parser.
cmSystemToolsArgV argv(cmsysSystem_Parse_CommandForUnix(command, 0));
argv.Store(args);
}
std::vector<std::string> cmSystemTools::HandleResponseFile(
std::vector<std::string>::const_iterator argBeg,
std::vector<std::string>::const_iterator argEnd)
{
std::vector<std::string> arg_full;
for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
if (cmHasPrefix(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<std::string> 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<std::string> cmSystemTools::ParseArguments(std::string const& cmd)
{
std::vector<std::string> args;
std::string arg;
bool win_path = false;
char const* 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 (char const* 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)
{
char const* c = command.c_str();
// Skip leading whitespace.
while (cmsysString_isspace(*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 (cmsysString_isspace(*c)) {
break;
} else {
program += *c;
}
}
// The remainder of the command line holds unparsed arguments.
args = c;
return !in_single && !in_escape && !in_double;
}
std::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<size_t>(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<size_t>(szArgMax));
# else
sz = static_cast<size_t>(szArgMax);
# endif
}
#endif
return sz;
}
void cmSystemTools::MaybePrependCmdExe(std::vector<std::string>& cmdLine)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
if (!cmdLine.empty()) {
std::string& applicationName = cmdLine.at(0);
static cmsys::RegularExpression const winCmdRegex(
"\\.([Bb][Aa][Tt]|[Cc][Mm][Dd])$");
cmsys::RegularExpressionMatch winCmdMatch;
if (winCmdRegex.find(applicationName.c_str(), winCmdMatch)) {
// Wrap `.bat` and `.cmd` commands with `cmd /c call`.
std::vector<std::string> output;
output.reserve(cmdLine.size() + 3);
output.emplace_back(cmSystemTools::GetComspec());
output.emplace_back("/c");
output.emplace_back("call");
// Convert the batch file path to use backslashes for cmd.exe to parse.
std::replace(applicationName.begin(), applicationName.end(), '/', '\\');
output.emplace_back(applicationName);
std::move(cmdLine.begin() + 1, cmdLine.end(),
std::back_inserter(output));
cmdLine = std::move(output);
}
}
#else
static_cast<void>(cmdLine);
#endif
}
bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
std::string* captureStdOut,
std::string* captureStdErr, int* retVal,
char const* dir, OutputOption outputflag,
cmDuration timeout, Encoding encoding)
{
cmUVProcessChainBuilder builder;
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_INPUT, stdin)
.AddCommand(command);
if (dir) {
builder.SetWorkingDirectory(dir);
}
if (outputflag == OUTPUT_PASSTHROUGH) {
captureStdOut = nullptr;
captureStdErr = nullptr;
builder.SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, stdout)
.SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, stderr);
} else if (outputflag == OUTPUT_MERGE ||
(captureStdErr && captureStdErr == captureStdOut)) {
builder.SetMergedBuiltinStreams();
captureStdErr = nullptr;
} else {
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
.SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
}
assert(!captureStdErr || captureStdErr != captureStdOut);
auto chain = builder.Start();
bool timedOut = false;
cm::uv_timer_ptr timer;
if (timeout.count()) {
timer.init(chain.GetLoop(), &timedOut);
timer.start(
[](uv_timer_t* t) {
auto* timedOutPtr = static_cast<bool*>(t->data);
*timedOutPtr = true;
},
static_cast<uint64_t>(timeout.count() * 1000.0), 0,
cm::uv_update_time::yes);
}
std::vector<char> tempStdOut;
std::vector<char> tempStdErr;
bool outFinished = true;
bool errFinished = true;
cmProcessOutput processOutput(encoding);
std::unique_ptr<cmUVStreamReadHandle> outputHandle;
std::unique_ptr<cmUVStreamReadHandle> errorHandle;
if (outputflag != OUTPUT_PASSTHROUGH &&
(captureStdOut || captureStdErr || outputflag != OUTPUT_NONE)) {
auto startRead =
[&outputflag, &processOutput](
uv_stream_t* stream, std::string* captureStd,
std::vector<char>& tempStd, int id,
void (*outputFunc)(std::string const&),
bool& finished) -> std::unique_ptr<cmUVStreamReadHandle> {
finished = false;
return cmUVStreamRead(
stream,
[outputflag, &processOutput, captureStd, &tempStd, id,
outputFunc](std::vector<char> data) {
// Translate NULL characters in the output into valid text.
for (auto& c : data) {
if (c == '\0') {
c = ' ';
}
}
if (outputflag != OUTPUT_NONE) {
std::string strdata;
processOutput.DecodeText(data.data(), data.size(), strdata, id);
outputFunc(strdata);
}
if (captureStd) {
cm::append(tempStd, data.data(), data.data() + data.size());
}
},
[&finished, outputflag, &processOutput, id, outputFunc]() {
finished = true;
if (outputflag != OUTPUT_NONE) {
std::string strdata;
processOutput.DecodeText(std::string(), strdata, id);
if (!strdata.empty()) {
outputFunc(strdata);
}
}
});
};
outputHandle = startRead(chain.OutputStream(), captureStdOut, tempStdOut,
1, cmSystemTools::Stdout, outFinished);
if (chain.ErrorStream()) {
errorHandle = startRead(chain.ErrorStream(), captureStdErr, tempStdErr,
2, cmSystemTools::Stderr, errFinished);
}
}
while (!timedOut && !(chain.Finished() && outFinished && errFinished)) {
uv_run(&chain.GetLoop(), UV_RUN_ONCE);
}
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 (timedOut) {
chain.Terminate();
char const* 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;
} else {
auto const& status = chain.GetStatus(0);
auto exception = status.GetException();
switch (exception.first) {
case cmUVProcessChain::ExceptionCode::None:
if (retVal) {
*retVal = static_cast<int>(status.ExitStatus);
} else {
if (status.ExitStatus != 0) {
result = false;
}
}
break;
default: {
if (outputflag != OUTPUT_NONE) {
std::cerr << exception.second << std::endl;
}
if (captureStdErr) {
captureStdErr->append(exception.second);
} else if (captureStdOut) {
captureStdOut->append(exception.second);
}
result = false;
} break;
}
}
return result;
}
bool cmSystemTools::RunSingleCommand(std::string const& command,
std::string* captureStdOut,
std::string* captureStdErr, int* retVal,
char const* dir, OutputOption outputflag,
cmDuration timeout)
{
if (s_DisableRunCommandOutput) {
outputflag = OUTPUT_NONE;
}
std::vector<std::string> 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<std::string> const& command)
{
if (command.empty()) {
return std::string();
}
return cmWrap('"', command, '"', " ");
}
bool cmSystemTools::DoesFileExistWithExtensions(
std::string const& name, std::vector<std::string> const& 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(
std::string const& fname, std::string const& directory,
std::string const& 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<unsigned int>(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;
}
cmSystemTools::WindowsVersion cmSystemTools::GetWindowsVersion()
{
/* Windows version number data. */
OSVERSIONINFOEXW osviex;
ZeroMemory(&osviex, sizeof(osviex));
osviex.dwOSVersionInfoSize = sizeof(osviex);
# ifdef CM_WINDOWS_DEPRECATED_GetVersionEx
# pragma warning(push)
# ifdef __INTEL_COMPILER
# pragma warning(disable : 1478)
# elif defined __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
# else
# pragma warning(disable : 4996)
# endif
# endif
GetVersionExW((OSVERSIONINFOW*)&osviex);
# ifdef CM_WINDOWS_DEPRECATED_GetVersionEx
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma warning(pop)
# endif
# endif
WindowsVersion result;
result.dwMajorVersion = osviex.dwMajorVersion;
result.dwMinorVersion = osviex.dwMinorVersion;
result.dwBuildNumber = osviex.dwBuildNumber;
return result;
}
std::string cmSystemTools::GetComspec()
{
std::string comspec;
if (!cmSystemTools::GetEnv("COMSPEC", comspec) ||
!cmSystemTools::FileIsFullPath(comspec)) {
comspec = "cmd.exe";
}
return comspec;
}
#endif
// File changes involve removing SETUID/SETGID bits when a file is modified.
// This behavior is consistent across most Unix-like operating systems.
class FileModeGuard
{
public:
FileModeGuard(std::string const& file_path, std::string* emsg);
bool Restore(std::string* emsg);
bool HasErrors() const;
private:
#ifndef _WIN32
mode_t mode_;
#endif
std::string filepath_;
};
FileModeGuard::FileModeGuard(std::string const& file_path, std::string* emsg)
{
#ifndef _WIN32
struct stat file_stat;
if (stat(file_path.c_str(), &file_stat) != 0) {
if (emsg) {
*emsg = cmStrCat("Cannot get file stat: ", strerror(errno));
}
return;
}
mode_ = file_stat.st_mode;
#else
static_cast<void>(emsg);
#endif
filepath_ = file_path;
}
bool FileModeGuard::Restore(std::string* emsg)
{
assert(filepath_.empty() == false);
#ifndef _WIN32
struct stat file_stat;
if (stat(filepath_.c_str(), &file_stat) != 0) {
if (emsg) {
*emsg = cmStrCat("Cannot get file stat: ", strerror(errno));
}
return false;
}
// Nothing changed; everything is in the expected state
if (file_stat.st_mode == mode_) {
return true;
}
if (chmod(filepath_.c_str(), mode_) != 0) {
if (emsg) {
*emsg = cmStrCat("Cannot restore the file mode: ", strerror(errno));
}
return false;
}
#else
static_cast<void>(emsg);
#endif
return true;
}
bool FileModeGuard::HasErrors() const
{
return filepath_.empty();
}
std::string cmSystemTools::GetRealPathResolvingWindowsSubst(
std::string const& 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(nullptr, &req, path.c_str(), nullptr);
if (!err) {
resolved_path = std::string((char*)req.ptr);
cmSystemTools::ConvertToUnixSlashes(resolved_path);
} else if (err == UV_ENOSYS) {
resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage);
} else if (errorMessage) {
cmsys::Status status =
cmsys::Status::Windows(uv_fs_get_system_error(&req));
*errorMessage = status.GetString();
resolved_path.clear();
} else {
resolved_path = path;
}
// Normalize to upper-case drive letter as cm::PathResolver does.
if (resolved_path.size() > 1 && resolved_path[1] == ':') {
resolved_path[0] = cmsysString_toupper(resolved_path[0]);
}
return resolved_path;
#else
return cmsys::SystemTools::GetRealPath(path, errorMessage);
#endif
}
std::string cmSystemTools::GetRealPath(std::string const& path,
std::string* errorMessage)
{
#ifdef _WIN32
std::string resolved_path =
cmSystemTools::GetRealPathResolvingWindowsSubst(path, errorMessage);
// If the original path used a subst drive and the real path starts
// with the substitution, restore the subst drive prefix. This may
// incorrectly restore a subst drive if the underlying drive was
// encountered via an absolute symlink, but this is an acceptable
// limitation to otherwise preserve susbt drives.
if (resolved_path.size() >= 2 && resolved_path[1] == ':' &&
path.size() >= 2 && path[1] == ':' &&
cmsysString_toupper(resolved_path[0]) != cmsysString_toupper(path[0])) {
// FIXME: Add thread_local or mutex if we use threads.
static std::map<char, std::string> substMap;
char const drive = static_cast<char>(cmsysString_toupper(path[0]));
std::string maybe_subst = cmStrCat(drive, ":/");
auto smi = substMap.find(drive);
if (smi == substMap.end()) {
smi = substMap
.emplace(
drive,
cmSystemTools::GetRealPathResolvingWindowsSubst(maybe_subst))
.first;
}
std::string const& resolved_subst = smi->second;
std::string::size_type const ns = resolved_subst.size();
if (ns > 0) {
std::string::size_type const np = resolved_path.size();
if (ns == np && resolved_path == resolved_subst) {
resolved_path = maybe_subst;
} else if (ns > 0 && ns < np && resolved_path[ns] == '/' &&
resolved_path.compare(0, ns, resolved_subst) == 0) {
resolved_path.replace(0, ns + 1, maybe_subst);
}
}
}
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
}
#if defined(_WIN32)
# include <random>
# include <wctype.h>
# ifdef _MSC_VER
using mode_t = cmSystemTools::SystemTools::mode_t;
# endif
#else
# include <sys/stat.h>
#endif
inline int Mkdir(char const* dir, mode_t const* mode)
{
#if defined(_WIN32)
int ret = _wmkdir(cmSystemTools::ConvertToWindowsExtendedPath(dir).c_str());
if (ret == 0 && mode)
cmSystemTools::SystemTools::SetPermissions(dir, *mode);
return ret;
#else
return mkdir(dir, mode ? *mode : 0777);
#endif
}
#ifdef CMAKE_NO_MKDTEMP
namespace {
char* cm_mkdtemp_fallback(char* template_)
{
if (mktemp(template_) == nullptr || mkdir(template_, 0700) != 0) {
return nullptr;
}
return template_;
}
using cm_mkdtemp_t = char* (*)(char*);
cm_mkdtemp_t const cm_mkdtemp = []() -> cm_mkdtemp_t {
cm_mkdtemp_t f = (cm_mkdtemp_t)dlsym(RTLD_DEFAULT, "mkdtemp");
dlerror(); // Ignore/cleanup dlsym errors.
if (!f) {
f = cm_mkdtemp_fallback;
}
return f;
}();
}
#else
# define cm_mkdtemp mkdtemp
#endif
cmsys::Status cmSystemTools::MakeTempDirectory(std::string& path,
mode_t const* mode)
{
if (path.empty()) {
return cmsys::Status::POSIX(EINVAL);
}
return cmSystemTools::MakeTempDirectory(&path.front(), mode);
}
cmsys::Status cmSystemTools::MakeTempDirectory(char* path, mode_t const* mode)
{
if (!path) {
return cmsys::Status::POSIX(EINVAL);
}
// verify that path ends with "XXXXXX"
auto const l = std::strlen(path);
if (!cmHasLiteralSuffix(cm::string_view{ path, l }, "XXXXXX")) {
return cmsys::Status::POSIX(EINVAL);
}
// create parent directories
auto* sep = path;
while ((sep = strchr(sep, '/'))) {
// all underlying functions use C strings,
// so temporarily end the string here
*sep = '\0';
Mkdir(path, mode);
*sep = '/';
++sep;
}
#ifdef _WIN32
int const nchars = 36;
char const chars[nchars + 1] = "abcdefghijklmnopqrstuvwxyz0123456789";
std::random_device rd;
std::mt19937 rg{ rd() };
std::uniform_int_distribution<int> dist{ 0, nchars - 1 };
for (auto tries = 100; tries; --tries) {
for (auto n = l - 6; n < l; ++n) {
path[n] = chars[dist(rg)];
}
if (Mkdir(path, mode) == 0) {
return cmsys::Status::Success();
} else if (errno != EEXIST) {
return cmsys::Status::POSIX_errno();
}
}
return cmsys::Status::POSIX(EAGAIN);
#else
if (cm_mkdtemp(path)) {
if (mode) {
chmod(path, *mode);
}
} else {
return cmsys::Status::POSIX_errno();
}
return cmsys::Status::Success();
#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
cmSystemTools::CopyResult cmSystemTools::CopySingleFile(
std::string const& oldname, std::string const& newname, CopyWhen when,
CopyInputRecent inputRecent, std::string* err)
{
switch (when) {
case CopyWhen::Always:
break;
case CopyWhen::OnlyIfDifferent:
if (!FilesDiffer(oldname, newname)) {
return CopyResult::Success;
}
break;
case CopyWhen::OnlyIfNewer: {
if (!SystemTools::FileExists(newname)) {
break;
}
int timeResult = 0;
cmsys::Status timeStatus =
cmsys::SystemTools::FileTimeCompare(oldname, newname, &timeResult);
if (timeStatus.IsSuccess() && timeResult <= 0) {
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::SystemTools::CopyStatus status;
status = cmsys::SystemTools::CloneFileContent(oldname, newname);
if (!status) {
// if cloning did not succeed, fall back to blockwise copy
#ifdef _WIN32
if (inputRecent == CopyInputRecent::Yes) {
// Windows sometimes locks a file immediately after creation.
// Retry a few times.
WindowsFileRetry retry = cmSystemTools::GetWindowsFileRetry();
while ((status =
cmsys::SystemTools::CopyFileContentBlockwise(oldname, newname),
status.Path == cmsys::SystemTools::CopyStatus::SourcePath &&
status.GetPOSIX() == EACCES && --retry.Count)) {
cmSystemTools::Delay(retry.Delay);
}
} else {
status = cmsys::SystemTools::CopyFileContentBlockwise(oldname, newname);
}
#else
static_cast<void>(inputRecent);
status = cmsys::SystemTools::CopyFileContentBlockwise(oldname, newname);
#endif
}
if (!status) {
if (err) {
*err = status.GetString();
switch (status.Path) {
case cmsys::SystemTools::CopyStatus::SourcePath:
*err = cmStrCat(*err, " (input)");
break;
case cmsys::SystemTools::CopyStatus::DestPath:
*err = cmStrCat(*err, " (output)");
break;
default:
break;
}
}
return CopyResult::Failure;
}
if (perms) {
perms = SystemTools::SetPermissions(newname, perm);
if (!perms) {
if (err) {
*err = cmStrCat(perms.GetString(), " (output)");
}
return CopyResult::Failure;
}
}
return CopyResult::Success;
}
bool cmSystemTools::RenameFile(std::string const& oldname,
std::string const& 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) {
if (unlink(oldname.c_str()) == 0) {
return RenameResult::Success;
}
if (err) {
*err = cmsys::Status::POSIX_errno().GetString();
}
return RenameResult::Failure;
}
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
}
cmsys::Status cmSystemTools::MoveFileIfDifferent(
std::string const& source, std::string const& destination)
{
cmsys::Status res = {};
if (FilesDiffer(source, destination)) {
if (RenameFile(source, destination)) {
return res;
}
res = CopyFileAlways(source, destination);
}
RemoveFile(source);
return res;
}
void cmSystemTools::Glob(std::string const& directory,
std::string const& regexp,
std::vector<std::string>& 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(std::string const& path,
std::vector<std::string>& 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(std::string const& glob,
std::vector<std::string>& 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<void>(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<wchar_t> buffer(ret);
if (ret != 0) {
ret = GetLongPathNameW(wPath.c_str(), buffer.data(),
static_cast<DWORD>(buffer.size()));
}
if (ret != 0) {
path = cmsys::Encoding::ToNarrow(buffer.data());
}
#else
static_cast<void>(path);
#endif
}
std::string cmSystemTools::ConvertToRunCommandPath(std::string const& 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<std::string> local;
cmSystemTools::SplitPath(local_path, local);
std::vector<std::string> 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() + (top.back() == '/' ? 0 : 1));
} else {
out = in;
}
return out;
}
cm::optional<std::string> cmSystemTools::GetEnvVar(std::string const& var)
{
cm::optional<std::string> result;
{
std::string value;
if (cmSystemTools::GetEnv(var, value)) {
result = std::move(value);
}
}
return result;
}
std::vector<std::string> cmSystemTools::GetEnvPathNormalized(
std::string const& var)
{
std::vector<std::string> result;
if (cm::optional<std::string> env = cmSystemTools::GetEnvVar(var)) {
std::vector<std::string> p = cmSystemTools::SplitEnvPathNormalized(*env);
std::move(p.begin(), p.end(), std::back_inserter(result));
}
return result;
}
std::vector<std::string> cmSystemTools::SplitEnvPath(cm::string_view in)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
static cm::string_view sep = ";"_s;
#else
static cm::string_view sep = ":"_s;
#endif
std::vector<std::string> paths;
cm::string_view::size_type e = 0;
for (;;) {
cm::string_view::size_type b = in.find_first_not_of(sep, e);
if (b == cm::string_view::npos) {
break;
}
e = in.find_first_of(sep, b);
if (e == cm::string_view::npos) {
paths.emplace_back(in.substr(b));
break;
}
paths.emplace_back(in.substr(b, e - b));
}
return paths;
}
std::vector<std::string> cmSystemTools::SplitEnvPathNormalized(
cm::string_view in)
{
std::vector<std::string> paths = cmSystemTools::SplitEnvPath(in);
std::transform(paths.begin(), paths.end(), paths.begin(),
cmSystemTools::ToNormalizedPathOnDisk);
return paths;
}
std::string cmSystemTools::ToNormalizedPathOnDisk(std::string p)
{
p = ResolveTildePath(p);
using namespace cm::PathResolver;
#ifdef _WIN32
// IWYU pragma: no_forward_declare cm::PathResolver::Policies::CasePath
static Resolver<Policies::CasePath> const resolver(RealOS);
#else
// IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath
static Resolver<Policies::LogicalPath> const resolver(RealOS);
#endif
resolver.Resolve(std::move(p), p);
return p;
}
#ifndef CMAKE_BOOTSTRAP
bool cmSystemTools::UnsetEnv(char const* value)
{
# if !defined(HAVE_UNSETENV)
return cmSystemTools::UnPutEnv(value);
# else
unsetenv(value);
return true;
# endif
}
std::vector<std::string> cmSystemTools::GetEnvironmentVariables()
{
std::vector<std::string> env;
int cc;
# ifdef _WIN32
// if program starts with main, _wenviron is initially NULL, call to
// _wgetenv and create wide-character string environment
_wgetenv(L"");
for (cc = 0; _wenviron[cc]; ++cc) {
env.emplace_back(cmsys::Encoding::ToNarrow(_wenviron[cc]));
}
# else
for (cc = 0; environ[cc]; ++cc) {
env.emplace_back(environ[cc]);
}
# endif
return env;
}
void cmSystemTools::AppendEnv(std::vector<std::string> const& env)
{
for (std::string const& var : env) {
cmSystemTools::PutEnv(var);
}
}
void cmSystemTools::EnvDiff::AppendEnv(std::vector<std::string> const& env)
{
for (std::string const& var : env) {
this->PutEnv(var);
}
}
void cmSystemTools::EnvDiff::PutEnv(std::string const& env)
{
auto const eq_loc = env.find('=');
if (eq_loc != std::string::npos) {
std::string name = env.substr(0, eq_loc);
diff[name] = env.substr(eq_loc + 1);
} else {
this->UnPutEnv(env);
}
}
void cmSystemTools::EnvDiff::UnPutEnv(std::string const& env)
{
diff[env] = cm::nullopt;
}
bool cmSystemTools::EnvDiff::ParseOperation(std::string const& envmod)
{
char path_sep = GetSystemPathlistSeparator();
auto apply_diff = [this](std::string const& name,
std::function<void(std::string&)> const& apply) {
cm::optional<std::string> old_value = diff[name];
std::string output;
if (old_value) {
output = *old_value;
} else {
char const* curval = cmSystemTools::GetEnv(name);
if (curval) {
output = curval;
}
}
apply(output);
diff[name] = output;
};
// Split on `=`
auto const eq_loc = envmod.find_first_of('=');
if (eq_loc == std::string::npos) {
cmSystemTools::Error(cmStrCat(
"Error: Missing `=` after the variable name in: ", envmod, '\n'));
return false;
}
auto const name = envmod.substr(0, eq_loc);
// Split value on `:`
auto const op_value_start = eq_loc + 1;
auto const colon_loc = envmod.find_first_of(':', op_value_start);
if (colon_loc == std::string::npos) {
cmSystemTools::Error(
cmStrCat("Error: Missing `:` after the operation in: ", envmod, '\n'));
return false;
}
auto const op = envmod.substr(op_value_start, colon_loc - op_value_start);
auto const value_start = colon_loc + 1;
auto const value = envmod.substr(value_start);
// Determine what to do with the operation.
if (op == "reset"_s) {
auto entry = diff.find(name);
if (entry != diff.end()) {
diff.erase(entry);
}
} else if (op == "set"_s) {
diff[name] = value;
} else if (op == "unset"_s) {
diff[name] = cm::nullopt;
} else if (op == "string_append"_s) {
apply_diff(name, [&value](std::string& output) { output += value; });
} else if (op == "string_prepend"_s) {
apply_diff(name,
[&value](std::string& output) { output.insert(0, value); });
} else if (op == "path_list_append"_s) {
apply_diff(name, [&value, path_sep](std::string& output) {
if (!output.empty()) {
output += path_sep;
}
output += value;
});
} else if (op == "path_list_prepend"_s) {
apply_diff(name, [&value, path_sep](std::string& output) {
if (!output.empty()) {
output.insert(output.begin(), path_sep);
}
output.insert(0, value);
});
} else if (op == "cmake_list_append"_s) {
apply_diff(name, [&value](std::string& output) {
if (!output.empty()) {
output += ';';
}
output += value;
});
} else if (op == "cmake_list_prepend"_s) {
apply_diff(name, [&value](std::string& output) {
if (!output.empty()) {
output.insert(output.begin(), ';');
}
output.insert(0, value);
});
} else {
cmSystemTools::Error(cmStrCat(
"Error: Unrecognized environment manipulation argument: ", op, '\n'));
return false;
}
return true;
}
void cmSystemTools::EnvDiff::ApplyToCurrentEnv(std::ostringstream* measurement)
{
for (auto const& env_apply : diff) {
if (env_apply.second) {
auto const env_update =
cmStrCat(env_apply.first, '=', *env_apply.second);
cmSystemTools::PutEnv(env_update);
if (measurement) {
*measurement << env_update << std::endl;
}
} else {
cmSystemTools::UnsetEnv(env_apply.first.c_str());
if (measurement) {
// Signify that this variable is being actively unset
*measurement << '#' << env_apply.first << "=\n";
}
}
}
}
cmSystemTools::SaveRestoreEnvironment::SaveRestoreEnvironment()
{
this->Env = cmSystemTools::GetEnvironmentVariables();
}
cmSystemTools::SaveRestoreEnvironment::~SaveRestoreEnvironment()
{
// First clear everything in the current environment:
std::vector<std::string> 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
cmSystemTools::ScopedEnv::ScopedEnv(cm::string_view var)
{
std::string::size_type pos = var.find('=');
if (pos != std::string::npos) {
this->Key = std::string{ var.substr(0, pos) };
this->Original = cmSystemTools::GetEnvVar(this->Key);
cm::string_view value = var.substr(pos + 1);
if (!this->Original && value.empty()) {
// nothing to do if the environment variable wasn't already set and the
// new value is also empty. clear the Key member so the destructor also
// does nothing.
this->Key.clear();
} else {
if (value.empty()) {
cmSystemTools::UnPutEnv(this->Key);
} else {
cmSystemTools::PutEnv(cmStrCat(this->Key, '=', value));
}
}
}
}
cmSystemTools::ScopedEnv::~ScopedEnv()
{
if (!this->Key.empty()) {
if (this->Original) {
cmSystemTools::PutEnv(cmStrCat(this->Key, '=', *this->Original));
} else {
cmSystemTools::UnPutEnv(Key);
}
}
}
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(std::string const& path)
{
return (cmSystemTools::FileIsFullPath(path) &&
cmHasLiteralSuffix(path, ".framework"));
}
bool cmSystemTools::IsPathToXcFramework(std::string const& path)
{
return (cmSystemTools::FileIsFullPath(path) &&
cmHasLiteralSuffix(path, ".xcframework"));
}
bool cmSystemTools::IsPathToMacOSSharedLibrary(std::string const& path)
{
return (cmSystemTools::FileIsFullPath(path) &&
cmHasLiteralSuffix(path, ".dylib"));
}
bool cmSystemTools::CreateTar(std::string const& arFileName,
std::vector<std::string> const& files,
std::string const& workingDirectory,
cmTarCompression compressType, bool verbose,
std::string const& mtime,
std::string const& format, int compressionLevel,
int numThreads)
{
#if !defined(CMAKE_BOOTSTRAP)
cmWorkingDirectory workdir(cmSystemTools::GetLogicalWorkingDirectory());
if (!workingDirectory.empty()) {
workdir.SetDirectory(workingDirectory);
}
std::string const cwd = cmSystemTools::GetLogicalWorkingDirectory();
cmsys::ofstream fout(arFileName.c_str(), std::ios::out | std::ios::binary);
if (!fout) {
std::string e = cmStrCat("Cannot open output file \"", arFileName,
"\": ", 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 TarCompressLZMA:
compress = cmArchiveWrite::CompressLZMA;
break;
case TarCompressPPMd:
compress = cmArchiveWrite::CompressPPMd;
break;
case TarCompressAuto:
// Kept for backwards compatibility with pre-4.3 versions of CMake
if (format == "zip") {
compress = cmArchiveWrite::CompressGZip;
} else if (format == "7zip") {
compress = cmArchiveWrite::CompressLZMA;
} else {
compress = cmArchiveWrite::CompressNone;
}
break;
case TarCompressNone:
compress = cmArchiveWrite::CompressNone;
break;
}
cmArchiveWrite a(fout, compress, format.empty() ? "paxr" : format,
compressionLevel, numThreads);
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)arFileName;
(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;
char const* p;
char const* 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 %u ", archive_entry_strmode(entry),
archive_entry_nlink(entry));
/* Use uname if it's present, else uid. */
p = archive_entry_uname(entry);
if (!p || (*p == '\0')) {
snprintf(tmp, sizeof(tmp), "%lu ",
static_cast<unsigned long>(archive_entry_uid(entry)));
p = tmp;
}
w = strlen(p);
if (w > u_width) {
u_width = w;
}
fprintf(out, "%-*s ", static_cast<int>(u_width), p);
/* Use gname if it's present, else gid. */
p = archive_entry_gname(entry);
if (p && p[0] != '\0') {
fprintf(out, "%s", p);
w = strlen(p);
} else {
snprintf(tmp, sizeof(tmp), "%lu",
static_cast<unsigned long>(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<BSDTAR_FILESIZE_TYPE>(archive_entry_size(entry)));
}
if (w + strlen(tmp) >= gs_width) {
gs_width = w + strlen(tmp) + 1;
}
fprintf(out, "%*s", static_cast<int>(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));
/* 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(char const* m1, struct archive* a)
{
std::string message(m1);
char const* 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) {
char const* warn = archive_error_string(ar);
if (!warn) {
warn = "unknown warning";
}
std::cerr << "cmake -E tar: warning: " << warn << '\n';
return true;
}
// Error.
char const* 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;
void const* 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(std::string const& arFileName,
std::vector<std::string> const& files, bool verbose,
cmSystemTools::cmTarExtractTimestamps extractTimestamps,
bool extract)
{
struct archive* a = archive_read_new();
struct archive* ext = archive_write_disk_new();
if (extract) {
int flags =
ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;
if (extractTimestamps == cmSystemTools::cmTarExtractTimestamps::Yes) {
flags |= ARCHIVE_EXTRACT_TIME;
}
if (archive_write_disk_set_options(ext, flags) != ARCHIVE_OK) {
ArchiveError("Problem with archive_write_disk_set_options(): ", ext);
archive_write_free(ext);
archive_read_free(a);
return false;
}
}
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
struct archive_entry* entry;
struct archive* matching = archive_match_new();
if (!matching) {
cmSystemTools::Error("Out of memory");
return false;
}
for (auto const& 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_filename(a, arFileName.c_str(), 10240);
if (r) {
ArchiveError("Problem with archive_read_open_filename(): ", a);
archive_write_free(ext);
archive_read_close(a);
archive_read_free(a);
archive_match_free(matching);
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(
cmStrCat("x ", cm_archive_entry_pathname(entry)));
} else {
list_item_verbose(stdout, entry);
}
cmSystemTools::Stdout("\n");
} else if (!extract) {
cmSystemTools::Stdout(cmStrCat(cm_archive_entry_pathname(entry), '\n'));
}
if (extract) {
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 (char const* 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(
cmStrCat("Current file:\n ", cm_archive_entry_pathname(entry)));
break;
}
}
}
bool error_occurred = false;
if (matching) {
char const* 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_occurred = true;
}
if (error_occurred) {
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(std::string const& arFileName,
std::vector<std::string> const& files,
cmTarExtractTimestamps extractTimestamps,
bool verbose)
{
#if !defined(CMAKE_BOOTSTRAP)
return extract_tar(arFileName, files, verbose, extractTimestamps, true);
#else
(void)arFileName;
(void)files;
(void)extractTimestamps;
(void)verbose;
return false;
#endif
}
bool cmSystemTools::ListTar(std::string const& arFileName,
std::vector<std::string> const& files,
bool verbose)
{
#if !defined(CMAKE_BOOTSTRAP)
return extract_tar(arFileName, files, verbose, cmTarExtractTimestamps::Yes,
false);
#else
(void)arFileName;
(void)files;
(void)verbose;
return false;
#endif
}
cmSystemTools::WaitForLineResult cmSystemTools::WaitForLine(
uv_loop_t* loop, uv_stream_t* outPipe, uv_stream_t* errPipe,
std::string& line, std::vector<char>& out, std::vector<char>& 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<char>::size_type length = outiter - out.begin();
if (length > 1 && *(outiter - 1) == '\r') {
--length;
}
if (length > 0) {
line.append(out.data(), length);
}
out.erase(out.begin(), outiter + 1);
return WaitForLineResult::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<char>::size_type length = erriter - err.begin();
if (length > 1 && *(erriter - 1) == '\r') {
--length;
}
if (length > 0) {
line.append(err.data(), length);
}
err.erase(err.begin(), erriter + 1);
return WaitForLineResult::STDERR;
}
}
// No newlines found. Wait for more data from the process.
struct ReadData
{
uv_stream_t* Stream;
std::vector<char> Buffer;
bool Read = false;
bool Finished = false;
};
auto startRead =
[](uv_stream_t* stream,
ReadData& data) -> std::unique_ptr<cmUVStreamReadHandle> {
data.Stream = stream;
return cmUVStreamRead(
stream,
[&data](std::vector<char> buf) {
data.Buffer = std::move(buf);
data.Read = true;
uv_read_stop(data.Stream);
},
[&data]() { data.Finished = true; });
};
ReadData outData;
auto outHandle = startRead(outPipe, outData);
ReadData errData;
auto errHandle = startRead(errPipe, errData);
uv_run(loop, UV_RUN_ONCE);
if (outData.Read) {
processOutput.DecodeText(outData.Buffer.data(), outData.Buffer.size(),
strdata, 1);
// Append to the stdout buffer.
std::vector<char>::size_type size = out.size();
cm::append(out, strdata);
outiter = out.begin() + size;
} else if (errData.Read) {
processOutput.DecodeText(errData.Buffer.data(), errData.Buffer.size(),
strdata, 2);
// Append to the stderr buffer.
std::vector<char>::size_type size = err.size();
cm::append(err, strdata);
erriter = err.begin() + size;
} else if (outData.Finished && errData.Finished) {
// Both stdout and stderr pipes have broken. Return leftover data.
processOutput.DecodeText(std::string(), strdata, 1);
if (!strdata.empty()) {
std::vector<char>::size_type size = out.size();
cm::append(out, strdata);
outiter = out.begin() + size;
}
processOutput.DecodeText(std::string(), strdata, 2);
if (!strdata.empty()) {
std::vector<char>::size_type size = err.size();
cm::append(err, strdata);
erriter = err.begin() + size;
}
if (!out.empty()) {
line.append(out.data(), outiter - out.begin());
out.erase(out.begin(), out.end());
return WaitForLineResult::STDOUT;
}
if (!err.empty()) {
line.append(err.data(), erriter - err.begin());
err.erase(err.begin(), err.end());
return WaitForLineResult::STDERR;
}
return WaitForLineResult::None;
}
if (!outData.Finished) {
uv_read_stop(outPipe);
}
if (!errData.Finished) {
uv_read_stop(errPipe);
}
}
}
#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<unsigned int>(ft.dwHighDateTime);
unsigned int t2 = static_cast<unsigned int>(ft.dwLowDateTime);
unsigned int pid = static_cast<unsigned int>(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<unsigned int>(getpid());
unsigned int tv_sec = static_cast<unsigned int>(t.tv_sec);
unsigned int tv_usec = static_cast<unsigned int>(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
}
unsigned int cmSystemTools::RandomNumber()
{
#ifndef CM_HAVE_THREAD_LOCAL
static std::mutex gen_mutex;
std::lock_guard<std::mutex> gen_mutex_lock(gen_mutex);
#else
thread_local
#endif
static std::mt19937 gen{ cmSystemTools::RandomSeed() };
return static_cast<unsigned int>(gen());
}
std::string cmSystemTools::FindProgram(std::string const& name,
std::vector<std::string> const& path)
{
std::string exe = cmsys::SystemTools::FindProgram(name, path);
if (!exe.empty()) {
exe = cmSystemTools::ToNormalizedPathOnDisk(std::move(exe));
}
return exe;
}
namespace {
std::string InitLogicalWorkingDirectory()
{
std::string cwd = cmsys::SystemTools::GetCurrentWorkingDirectory();
std::string pwd;
if (cmSystemTools::GetEnv("PWD", pwd) &&
cmSystemTools::FileIsFullPath(pwd)) {
std::string const pwd_real = cmSystemTools::GetRealPath(pwd);
if (pwd_real == cwd) {
cwd = cmSystemTools::ToNormalizedPathOnDisk(std::move(pwd));
}
}
return cwd;
}
std::string cmSystemToolsLogicalWorkingDirectory =
InitLogicalWorkingDirectory();
std::string cmSystemToolsCMakeCommand;
std::string cmSystemToolsCTestCommand;
std::string cmSystemToolsCPackCommand;
std::string cmSystemToolsCMakeCursesCommand;
std::string cmSystemToolsCMakeGUICommand;
std::string cmSystemToolsCMClDepsCommand;
std::string cmSystemToolsCMakeRoot;
std::string cmSystemToolsHTMLDoc;
#if defined(__APPLE__)
bool IsCMakeAppBundleExe(std::string const& exe)
{
return cmHasLiteralSuffix(cmSystemTools::LowerCase(exe), "/macos/cmake");
}
#endif
std::string FindOwnExecutable(char const* argv0)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
static_cast<void>(argv0);
wchar_t modulepath[_MAX_PATH];
::GetModuleFileNameW(nullptr, modulepath, sizeof(modulepath));
std::string exe = cmsys::Encoding::ToNarrow(modulepath);
#elif defined(__APPLE__)
static_cast<void>(argv0);
# 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<char*>(malloc(exe_path_size));
_NSGetExecutablePath(exe_path, &exe_path_size);
}
std::string exe = exe_path;
if (exe_path != exe_path_local) {
free(exe_path);
}
if (IsCMakeAppBundleExe(exe)) {
// The executable is inside an application bundle.
// The install tree has "..<CMAKE_BIN_DIR>/cmake-gui".
// The build tree has '../../../cmake-gui".
std::string dir = cmSystemTools::GetFilenamePath(exe);
dir = cmSystemTools::GetFilenamePath(dir);
exe = cmStrCat(dir, CMAKE_BIN_DIR "/cmake-gui");
if (!cmSystemTools::PathExists(exe)) {
dir = cmSystemTools::GetFilenamePath(dir);
dir = cmSystemTools::GetFilenamePath(dir);
exe = cmStrCat(dir, "/cmake-gui");
}
}
#else
std::string exe = cmsys::SystemTools::FindProgram(argv0);
#endif
exe = cmSystemTools::ToNormalizedPathOnDisk(std::move(exe));
return exe;
}
#ifndef CMAKE_BOOTSTRAP
bool ResolveSymlinkToOwnExecutable(std::string& exe, std::string& exe_dir)
{
std::string linked_exe;
if (!cmSystemTools::ReadSymlink(exe, linked_exe)) {
return false;
}
# if defined(__APPLE__)
// Ignore "cmake-gui -> ../MacOS/CMake".
if (IsCMakeAppBundleExe(linked_exe)) {
return false;
}
# endif
if (cmSystemTools::FileIsFullPath(linked_exe)) {
exe = std::move(linked_exe);
} else {
exe = cmStrCat(exe_dir, '/', std::move(linked_exe));
}
exe = cmSystemTools::ToNormalizedPathOnDisk(std::move(exe));
exe_dir = cmSystemTools::GetFilenamePath(exe);
return true;
}
bool FindCMakeResourcesInInstallTree(std::string const& exe_dir)
{
// Install tree has
// - "<prefix><CMAKE_BIN_DIR>/cmake"
// - "<prefix><CMAKE_DATA_DIR>"
// - "<prefix><CMAKE_DOC_DIR>"
if (cmHasLiteralSuffix(exe_dir, CMAKE_BIN_DIR)) {
std::string const prefix =
exe_dir.substr(0, exe_dir.size() - cmStrLen(CMAKE_BIN_DIR));
// Set cmSystemToolsCMakeRoot set to the location expected in an
// install tree, even if it does not exist, so that
// cmake::AddCMakePaths can print the location in its error message.
cmSystemToolsCMakeRoot = cmStrCat(prefix, CMAKE_DATA_DIR);
if (cmSystemTools::FileExists(
cmStrCat(cmSystemToolsCMakeRoot, "/Modules/CMake.cmake"))) {
if (cmSystemTools::FileExists(
cmStrCat(prefix, CMAKE_DOC_DIR "/html/index.html"))) {
cmSystemToolsHTMLDoc = cmStrCat(prefix, CMAKE_DOC_DIR "/html");
}
return true;
}
}
return false;
}
void FindCMakeResourcesInBuildTree(std::string const& exe_dir)
{
// Build tree has "<build>/bin[/<config>]/cmake" and
// "<build>/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");
}
}
#endif
}
void cmSystemTools::FindCMakeResources(char const* argv0)
{
std::string exe = FindOwnExecutable(argv0);
#ifdef CMAKE_BOOTSTRAP
// The bootstrap cmake knows its resource locations.
cmSystemToolsCMakeRoot = CMAKE_BOOTSTRAP_SOURCE_DIR;
cmSystemToolsCMakeCommand = exe;
// The bootstrap cmake does not provide the other tools,
// so use the directory where they are about to be built.
std::string exe_dir = CMAKE_BOOTSTRAP_BINARY_DIR "/bin";
#else
// Find resources relative to our own executable.
std::string exe_dir = cmSystemTools::GetFilenamePath(exe);
bool found = false;
// When running through a symlink to our own executable,
// preserve symlinks in directory components if possible.
do {
found = FindCMakeResourcesInInstallTree(exe_dir);
} while (!found && ResolveSymlinkToOwnExecutable(exe, exe_dir));
// If we have not yet found the resources, the above loop will
// have left 'exe' referring to a real file, not a symlink, so
// all our binaries should exist under 'exe_dir'. However, the
// resources may be discoverable only in the real path.
if (!found) {
found =
FindCMakeResourcesInInstallTree(cmSystemTools::GetRealPath(exe_dir));
}
if (!found) {
FindCMakeResourcesInBuildTree(exe_dir);
}
cmSystemToolsCMakeCommand =
cmStrCat(exe_dir, "/cmake", cmSystemTools::GetExecutableExtension());
#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();
}
}
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;
}
cm::optional<std::string> cmSystemTools::GetSystemConfigDirectory()
{
#if defined(_WIN32)
LPWSTR lpwstr;
if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &lpwstr))) {
return cm::nullopt;
}
std::wstring wstr = std::wstring(lpwstr);
CoTaskMemFree(lpwstr);
std::string config = cmsys::Encoding::ToNarrow(wstr);
cmSystemTools::ConvertToUnixSlashes(config);
return config;
#else
auto config = cmSystemTools::GetEnvVar("XDG_CONFIG_HOME");
if (!config.has_value()) {
config = cmSystemTools::GetEnvVar("HOME");
if (config.has_value()) {
# if defined(__APPLE__)
config = cmStrCat(config.value(), "/Library/Application Support");
# else
config = cmStrCat(config.value(), "/.config");
# endif
}
}
return config;
#endif
}
cm::optional<std::string> cmSystemTools::GetCMakeConfigDirectory()
{
auto config = cmSystemTools::GetEnvVar("CMAKE_CONFIG_DIR");
if (!config.has_value()) {
config = cmSystemTools::GetSystemConfigDirectory();
if (config.has_value()) {
#if defined(_WIN32) || defined(__APPLE__)
config = cmStrCat(config.value(), "/CMake");
#else
config = cmStrCat(config.value(), "/cmake");
#endif
}
}
return config;
}
std::string const& cmSystemTools::GetLogicalWorkingDirectory()
{
return cmSystemToolsLogicalWorkingDirectory;
}
cmsys::Status cmSystemTools::SetLogicalWorkingDirectory(std::string const& lwd)
{
cmsys::Status status = cmSystemTools::ChangeDirectory(lwd);
if (status) {
cmSystemToolsLogicalWorkingDirectory = lwd;
}
return status;
}
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 have,
cm::string_view 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<bool(std::string*, cmELF const&)>;
using AdjustCallback = std::function<bool(
cm::optional<std::string>&, std::string const&, char const*, std::string*)>;
cm::optional<bool> AdjustRPathELF(std::string const& file,
EmptyCallback const& emptyCallback,
AdjustCallback const& 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.
}
if (!elf.HasDynamicSection()) {
return true; // No dynamic section to update.
}
// Get the RPATH and RUNPATH entries from it.
int se_count = 0;
cmELF::StringEntry const* se[2] = { nullptr, nullptr };
char const* 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<std::string> 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);
}
FileModeGuard file_mode_guard(file, emsg);
if (file_mode_guard.HasErrors()) {
return false;
}
{
// 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;
}
}
}
if (!file_mode_guard.Restore(emsg)) {
return false;
}
// Everything was updated successfully.
if (changed) {
*changed = true;
}
return true;
}
std::function<bool(std::string*, cmELF const&)> MakeEmptyCallback(
std::string const& newRPath)
{
return [newRPath](std::string* emsg, cmELF const& 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<bool> 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<std::string>& outRPath,
std::string const& inRPath, char const* 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<bool> SetRPathELF(std::string const& file,
std::string const& newRPath,
std::string* emsg, bool* changed)
{
auto adjustCallback = [newRPath](cm::optional<std::string>& outRPath,
std::string const& inRPath,
char const* /*se_name*/, std::string*
/*emsg*/) -> bool {
if (inRPath != newRPath) {
outRPath = newRPath;
}
return true;
};
return AdjustRPathELF(file, MakeEmptyCallback(newRPath), adjustCallback,
emsg, changed);
}
static cm::optional<bool> 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<cm::string_view> 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<bool> 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<bool> result = ChangeRPathELF(
file, oldRPath, newRPath, removeEnvironmentRPath, emsg, changed)) {
return result.value();
}
if (cm::optional<bool> 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<bool> result = SetRPathELF(file, newRPath, emsg, changed)) {
return result.value();
}
if (cm::optional<bool> 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, char const* lhss,
char const* rhss)
{
char const* endl = lhss;
char const* endr = rhss;
while (((*endl >= '0') && (*endl <= '9')) ||
((*endr >= '0') && (*endr <= '9'))) {
// Do component-wise comparison, ignoring leading zeros
// (components are treated as integers, not as mantissas)
while (*endl == '0') {
endl++;
}
while (*endr == '0') {
endr++;
}
char const* beginl = endl;
char const* beginr = endr;
// count significant digits
while ((*endl >= '0') && (*endl <= '9')) {
endl++;
}
while ((*endr >= '0') && (*endr <= '9')) {
endr++;
}
// compare number of digits first
ptrdiff_t r = ((endl - beginl) - (endr - beginr));
if (r == 0) {
// compare the digits if number of digits is equal
r = strncmp(beginl, beginr, endl - beginl);
}
if (r < 0) {
// lhs < rhs, so true if operation is LESS
return (op & cmSystemTools::OP_LESS) != 0;
}
if (r > 0) {
// 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,
std::string const& lhs,
std::string const& rhs)
{
return ::VersionCompare(op, lhs.c_str(), rhs.c_str());
}
bool cmSystemTools::VersionCompare(cmSystemTools::CompareOp op,
std::string const& lhs, char const 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(char const* lhs,
char const* 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(char const* s, size_t i)
{
/* Step back until we are not preceded by a digit. */
while (i > 0 && cmsysString_isdigit(s[i - 1])) {
--i;
}
return i;
}
static size_t cm_strverscmp_find_digits_end(char const* s, size_t i)
{
/* Step forward over digits. */
while (cmsysString_isdigit(s[i])) {
++i;
}
return i;
}
static size_t cm_strverscmp_count_leading_zeros(char const* s, size_t b)
{
size_t i = b;
/* Step forward over zeros that are followed by another digit. */
while (s[i] == '0' && cmsysString_isdigit(s[i + 1])) {
++i;
}
return i - b;
}
static int cm_strverscmp(char const* lhs, char const* 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 ||
(cmsysString_isdigit(lhs[i]) && cmsysString_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<bool> 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<char> 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);
}
FileModeGuard file_mode_guard(file, emsg);
if (file_mode_guard.HasErrors()) {
return false;
}
// 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.data(), 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;
}
}
// Close the handle to allow further operations on the file
f.close();
if (!file_mode_guard.Restore(emsg)) {
return false;
}
// Everything was updated successfully.
if (removed) {
*removed = true;
}
return true;
}
static cm::optional<bool> 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
bool rm = false;
FileModeGuard file_mode_guard(file, emsg);
if (file_mode_guard.HasErrors()) {
return false;
}
{
cmXCOFF xcoff(file.c_str(), cmXCOFF::Mode::ReadWrite);
if (!xcoff) {
return cm::nullopt; // Not a valid XCOFF file.
}
rm = xcoff.RemoveLibPath();
if (!xcoff) {
if (emsg) {
*emsg = xcoff.GetErrorMessage();
}
return false;
}
}
if (!file_mode_guard.Restore(emsg)) {
return false;
}
if (removed) {
*removed = rm;
}
return true;
#endif
}
bool cmSystemTools::RemoveRPath(std::string const& file, std::string* emsg,
bool* removed)
{
if (cm::optional<bool> result = RemoveRPathELF(file, emsg, removed)) {
return result.value();
}
if (cm::optional<bool> 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<cm::string_view> 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();
}
cmsys::Status cmSystemTools::RepeatedRemoveDirectory(std::string const& dir)
{
#ifdef _WIN32
// Windows sometimes locks files temporarily so try a few times.
WindowsFileRetry retry = cmSystemTools::GetWindowsFileRetry();
cmsys::Status status;
unsigned int tries = 0;
while (!(status = cmSystemTools::RemoveADirectory(dir)) &&
++tries < retry.Count) {
cmSystemTools::Delay(retry.Delay);
}
return status;
#else
return 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<unsigned int>(c));
break;
case '/':
if (escapeSlashes) {
strcpy(hexCh, "%2F");
}
break;
default:
break;
}
out.append(hexCh);
}
return out;
}
cm::optional<cmSystemTools::DirCase> cmSystemTools::GetDirCase(
std::string const& dir)
{
if (!cmSystemTools::FileIsDirectory(dir)) {
return cm::nullopt;
}
#if defined(_WIN32) || defined(__APPLE__)
return DirCase::Insensitive;
#elif defined(__linux__)
int fd = open(dir.c_str(), O_RDONLY);
if (fd == -1) {
// cannot open dir but it exists, assume dir is case sensitive.
return DirCase::Sensitive;
}
int attr = 0;
int ioctl_res = ioctl(fd, FS_IOC_GETFLAGS, &attr);
close(fd);
if (ioctl_res == -1) {
return DirCase::Sensitive;
}
// FS_CASEFOLD_FD from linux/fs.h, in Linux libc-dev 5.4+
// For compat with old libc-dev, define it here.
int const CMAKE_FS_CASEFOLD_FL = 0x40000000;
return (attr & CMAKE_FS_CASEFOLD_FL) != 0 ? DirCase::Insensitive
: DirCase::Sensitive;
#else
return DirCase::Sensitive;
#endif
}
cmsys::Status cmSystemTools::CreateSymlink(std::string const& origName,
std::string const& newName)
{
cmsys::Status status =
cmSystemTools::CreateSymlinkQuietly(origName, newName);
if (!status) {
cmSystemTools::Error(cmStrCat("failed to create symbolic link '", newName,
"': ", status.GetString()));
}
return status;
}
cmsys::Status cmSystemTools::CreateSymlinkQuietly(std::string const& origName,
std::string const& newName)
{
uv_fs_t req;
int flags = 0;
#if defined(_WIN32)
if (cmsys::SystemTools::FileIsDirectory(origName)) {
flags |= UV_FS_SYMLINK_DIR;
}
#endif
int err = uv_fs_symlink(nullptr, &req, origName.c_str(), newName.c_str(),
flags, 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
}
return status;
}
cmsys::Status cmSystemTools::CreateLink(std::string const& origName,
std::string const& newName)
{
cmsys::Status status = cmSystemTools::CreateLinkQuietly(origName, newName);
if (!status) {
cmSystemTools::Error(
cmStrCat("failed to create link '", newName, "': ", status.GetString()));
}
return status;
}
cmsys::Status cmSystemTools::CreateLinkQuietly(std::string const& origName,
std::string const& newName)
{
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
}
return status;
}
cm::string_view cmSystemTools::GetSystemName()
{
#if defined(_WIN32)
return "Windows";
#elif defined(__MSYS__)
return "MSYS";
#elif defined(__CYGWIN__)
return "CYGWIN";
#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 cmsys::RegularExpression const bsdOsRegex("BSD.OS");
cmsys::RegularExpressionMatch match;
if (bsdOsRegex.find(uts_name.sysname, match)) {
systemName = "BSDOS";
}
return systemName;
}
return "";
#endif
}
char cmSystemTools::GetSystemPathlistSeparator()
{
#if defined(_WIN32)
return ';';
#else
return ':';
#endif
}
cm::string_view cmSystemTools::GetFilenameNameView(cm::string_view filename)
{
// implementation mostly taken from cmsys::SystemTools
#if defined(_WIN32) || defined(KWSYS_SYSTEMTOOLS_SUPPORT_WINDOWS_SLASHES)
cm::static_string_view separators = "/\\"_s;
#else
char separators = '/';
#endif
std::string::size_type slash_pos = filename.find_last_of(separators);
if (slash_pos == std::string::npos) {
return filename;
}
return filename.substr(slash_pos + 1);
}
cm::string_view cmSystemTools::GetFilenameLastExtensionView(
cm::string_view filename)
{
cm::string_view name = cmSystemTools::GetFilenameNameView(filename);
cm::string_view::size_type dot_pos = name.rfind('.');
if (dot_pos == std::string::npos) {
return cm::string_view();
}
return name.substr(dot_pos);
}