diff options
Diffstat (limited to 'Source/cmFileCommand.cxx')
-rw-r--r-- | Source/cmFileCommand.cxx | 3783 |
1 files changed, 3783 insertions, 0 deletions
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx new file mode 100644 index 0000000..59ef48d --- /dev/null +++ b/Source/cmFileCommand.cxx @@ -0,0 +1,3783 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmFileCommand.h" + +#include "cm_kwiml.h" +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include "cmsys/RegularExpression.hxx" + +#include <algorithm> +#include <assert.h> +#include <cmath> +#include <ctype.h> +#include <memory> // IWYU pragma: keep +#include <sstream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <utility> +#include <vector> + +#include "cmAlgorithms.h" +#include "cmCommandArgumentsHelper.h" +#include "cmCryptoHash.h" +#include "cmFSPermissions.h" +#include "cmFileLockPool.h" +#include "cmFileTimeComparison.h" +#include "cmGeneratorExpression.h" +#include "cmGlobalGenerator.h" +#include "cmHexFileConverter.h" +#include "cmInstallType.h" +#include "cmListFileCache.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmPolicies.h" +#include "cmRange.h" +#include "cmSystemTools.h" +#include "cmTimestamp.h" +#include "cm_sys_stat.h" +#include "cmake.h" + +#if defined(CMAKE_BUILD_WITH_CMAKE) +# include "cmCurl.h" +# include "cmFileLockResult.h" +# include "cm_curl.h" +#endif + +#if defined(CMAKE_USE_ELF_PARSER) +# include "cmELF.h" +#endif + +#if defined(_WIN32) +# include <windows.h> +#endif + +class cmSystemToolsFileTime; + +using namespace cmFSPermissions; + +#if defined(_WIN32) +// libcurl doesn't support file:// urls for unicode filenames on Windows. +// Convert string from UTF-8 to ACP if this is a file:// URL. +static std::string fix_file_url_windows(const std::string& url) +{ + std::string ret = url; + if (strncmp(url.c_str(), "file://", 7) == 0) { + std::wstring wurl = cmsys::Encoding::ToWide(url); + if (!wurl.empty()) { + int mblen = + WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, NULL, 0, NULL, NULL); + if (mblen > 0) { + std::vector<char> chars(mblen); + mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, &chars[0], + mblen, NULL, NULL); + if (mblen > 0) { + ret = &chars[0]; + } + } + } + } + return ret; +} +#endif + +// cmLibraryCommand +bool cmFileCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus&) +{ + if (args.size() < 2) { + this->SetError("must be called with at least two arguments."); + return false; + } + std::string const& subCommand = args[0]; + if (subCommand == "WRITE") { + return this->HandleWriteCommand(args, false); + } + if (subCommand == "APPEND") { + return this->HandleWriteCommand(args, true); + } + if (subCommand == "DOWNLOAD") { + return this->HandleDownloadCommand(args); + } + if (subCommand == "UPLOAD") { + return this->HandleUploadCommand(args); + } + if (subCommand == "READ") { + return this->HandleReadCommand(args); + } + if (subCommand == "MD5" || subCommand == "SHA1" || subCommand == "SHA224" || + subCommand == "SHA256" || subCommand == "SHA384" || + subCommand == "SHA512" || subCommand == "SHA3_224" || + subCommand == "SHA3_256" || subCommand == "SHA3_384" || + subCommand == "SHA3_512") { + return this->HandleHashCommand(args); + } + if (subCommand == "STRINGS") { + return this->HandleStringsCommand(args); + } + if (subCommand == "GLOB") { + return this->HandleGlobCommand(args, false); + } + if (subCommand == "GLOB_RECURSE") { + return this->HandleGlobCommand(args, true); + } + if (subCommand == "MAKE_DIRECTORY") { + return this->HandleMakeDirectoryCommand(args); + } + if (subCommand == "RENAME") { + return this->HandleRename(args); + } + if (subCommand == "REMOVE") { + return this->HandleRemove(args, false); + } + if (subCommand == "REMOVE_RECURSE") { + return this->HandleRemove(args, true); + } + if (subCommand == "COPY") { + return this->HandleCopyCommand(args); + } + if (subCommand == "INSTALL") { + return this->HandleInstallCommand(args); + } + if (subCommand == "DIFFERENT") { + return this->HandleDifferentCommand(args); + } + if (subCommand == "RPATH_CHANGE" || subCommand == "CHRPATH") { + return this->HandleRPathChangeCommand(args); + } + if (subCommand == "RPATH_CHECK") { + return this->HandleRPathCheckCommand(args); + } + if (subCommand == "RPATH_REMOVE") { + return this->HandleRPathRemoveCommand(args); + } + if (subCommand == "READ_ELF") { + return this->HandleReadElfCommand(args); + } + if (subCommand == "RELATIVE_PATH") { + return this->HandleRelativePathCommand(args); + } + if (subCommand == "TO_CMAKE_PATH") { + return this->HandleCMakePathCommand(args, false); + } + if (subCommand == "TO_NATIVE_PATH") { + return this->HandleCMakePathCommand(args, true); + } + if (subCommand == "TOUCH") { + return this->HandleTouchCommand(args, true); + } + if (subCommand == "TOUCH_NOCREATE") { + return this->HandleTouchCommand(args, false); + } + if (subCommand == "TIMESTAMP") { + return this->HandleTimestampCommand(args); + } + if (subCommand == "GENERATE") { + return this->HandleGenerateCommand(args); + } + if (subCommand == "LOCK") { + return this->HandleLockCommand(args); + } + if (subCommand == "SIZE") { + return this->HandleSizeCommand(args); + } + if (subCommand == "READ_SYMLINK") { + return this->HandleReadSymlinkCommand(args); + } + if (subCommand == "CREATE_LINK") { + return this->HandleCreateLinkCommand(args); + } + + std::string e = "does not recognize sub-command " + subCommand; + this->SetError(e); + return false; +} + +bool cmFileCommand::HandleWriteCommand(std::vector<std::string> const& args, + bool append) +{ + std::vector<std::string>::const_iterator i = args.begin(); + + i++; // Get rid of subcommand + + std::string fileName = *i; + if (!cmsys::SystemTools::FileIsFullPath(*i)) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + *i; + } + + i++; + + if (!this->Makefile->CanIWriteThisFile(fileName)) { + std::string e = + "attempted to write a file: " + fileName + " into a source directory."; + this->SetError(e); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + std::string dir = cmSystemTools::GetFilenamePath(fileName); + cmSystemTools::MakeDirectory(dir); + + mode_t mode = 0; + bool writable = false; + + // Set permissions to writable + if (cmSystemTools::GetPermissions(fileName, mode)) { +#if defined(_MSC_VER) || defined(__MINGW32__) + writable = (mode & S_IWRITE) != 0; + mode_t newMode = mode | S_IWRITE; +#else + writable = mode & S_IWUSR; + mode_t newMode = mode | S_IWUSR | S_IWGRP; +#endif + if (!writable) { + cmSystemTools::SetPermissions(fileName, newMode); + } + } + // If GetPermissions fails, pretend like it is ok. File open will fail if + // the file is not writable + cmsys::ofstream file(fileName.c_str(), + append ? std::ios::app : std::ios::out); + if (!file) { + std::string error = "failed to open for writing ("; + error += cmSystemTools::GetLastSystemError(); + error += "):\n "; + error += fileName; + this->SetError(error); + return false; + } + std::string message = cmJoin(cmMakeRange(i, args.end()), std::string()); + file << message; + if (!file) { + std::string error = "write failed ("; + error += cmSystemTools::GetLastSystemError(); + error += "):\n "; + error += fileName; + this->SetError(error); + return false; + } + file.close(); + if (mode && !writable) { + cmSystemTools::SetPermissions(fileName, mode); + } + return true; +} + +bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args) +{ + if (args.size() < 3) { + this->SetError("READ must be called with at least two additional " + "arguments"); + return false; + } + + cmCommandArgumentsHelper argHelper; + cmCommandArgumentGroup group; + + cmCAString readArg(&argHelper, "READ"); + cmCAString fileNameArg(&argHelper, nullptr); + cmCAString resultArg(&argHelper, nullptr); + + cmCAString offsetArg(&argHelper, "OFFSET", &group); + cmCAString limitArg(&argHelper, "LIMIT", &group); + cmCAEnabler hexOutputArg(&argHelper, "HEX", &group); + readArg.Follows(nullptr); + fileNameArg.Follows(&readArg); + resultArg.Follows(&fileNameArg); + group.Follows(&resultArg); + argHelper.Parse(&args, nullptr); + + std::string fileName = fileNameArg.GetString(); + if (!cmsys::SystemTools::FileIsFullPath(fileName)) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + fileNameArg.GetString(); + } + + std::string variable = resultArg.GetString(); + +// Open the specified file. +#if defined(_WIN32) || defined(__CYGWIN__) + cmsys::ifstream file( + fileName.c_str(), + std::ios::in | + (hexOutputArg.IsEnabled() ? std::ios::binary : std::ios::in)); +#else + cmsys::ifstream file(fileName.c_str()); +#endif + + if (!file) { + std::string error = "failed to open for reading ("; + error += cmSystemTools::GetLastSystemError(); + error += "):\n "; + error += fileName; + this->SetError(error); + return false; + } + + // is there a limit? + long sizeLimit = -1; + if (!limitArg.GetString().empty()) { + sizeLimit = atoi(limitArg.GetCString()); + } + + // is there an offset? + long offset = 0; + if (!offsetArg.GetString().empty()) { + offset = atoi(offsetArg.GetCString()); + } + + file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6 + + std::string output; + + if (hexOutputArg.IsEnabled()) { + // Convert part of the file into hex code + char c; + while ((sizeLimit != 0) && (file.get(c))) { + char hex[4]; + sprintf(hex, "%.2x", c & 0xff); + output += hex; + if (sizeLimit > 0) { + sizeLimit--; + } + } + } else { + std::string line; + bool has_newline = false; + while ( + sizeLimit != 0 && + cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) { + if (sizeLimit > 0) { + sizeLimit = sizeLimit - static_cast<long>(line.size()); + if (has_newline) { + sizeLimit--; + } + if (sizeLimit < 0) { + sizeLimit = 0; + } + } + output += line; + if (has_newline) { + output += "\n"; + } + } + } + this->Makefile->AddDefinition(variable, output.c_str()); + return true; +} + +bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + if (args.size() != 3) { + std::ostringstream e; + e << args[0] << " requires a file name and output variable"; + this->SetError(e.str()); + return false; + } + + std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str())); + if (hash) { + std::string out = hash->HashFile(args[1]); + if (!out.empty()) { + this->Makefile->AddDefinition(args[2], out.c_str()); + return true; + } + std::ostringstream e; + e << args[0] << " failed to read file \"" << args[1] + << "\": " << cmSystemTools::GetLastSystemError(); + this->SetError(e.str()); + } + return false; +#else + std::ostringstream e; + e << args[0] << " not available during bootstrap"; + this->SetError(e.str()); + return false; +#endif +} + +bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args) +{ + if (args.size() < 3) { + this->SetError("STRINGS requires a file name and output variable"); + return false; + } + + // Get the file to read. + std::string fileName = args[1]; + if (!cmsys::SystemTools::FileIsFullPath(fileName)) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + args[1]; + } + + // Get the variable in which to store the results. + std::string outVar = args[2]; + + // Parse the options. + enum + { + arg_none, + arg_limit_input, + arg_limit_output, + arg_limit_count, + arg_length_minimum, + arg_length_maximum, + arg__maximum, + arg_regex, + arg_encoding + }; + unsigned int minlen = 0; + unsigned int maxlen = 0; + int limit_input = -1; + int limit_output = -1; + unsigned int limit_count = 0; + cmsys::RegularExpression regex; + bool have_regex = false; + bool newline_consume = false; + bool hex_conversion_enabled = true; + enum + { + encoding_none = cmsys::FStream::BOM_None, + encoding_utf8 = cmsys::FStream::BOM_UTF8, + encoding_utf16le = cmsys::FStream::BOM_UTF16LE, + encoding_utf16be = cmsys::FStream::BOM_UTF16BE, + encoding_utf32le = cmsys::FStream::BOM_UTF32LE, + encoding_utf32be = cmsys::FStream::BOM_UTF32BE + }; + int encoding = encoding_none; + int arg_mode = arg_none; + for (unsigned int i = 3; i < args.size(); ++i) { + if (args[i] == "LIMIT_INPUT") { + arg_mode = arg_limit_input; + } else if (args[i] == "LIMIT_OUTPUT") { + arg_mode = arg_limit_output; + } else if (args[i] == "LIMIT_COUNT") { + arg_mode = arg_limit_count; + } else if (args[i] == "LENGTH_MINIMUM") { + arg_mode = arg_length_minimum; + } else if (args[i] == "LENGTH_MAXIMUM") { + arg_mode = arg_length_maximum; + } else if (args[i] == "REGEX") { + arg_mode = arg_regex; + } else if (args[i] == "NEWLINE_CONSUME") { + newline_consume = true; + arg_mode = arg_none; + } else if (args[i] == "NO_HEX_CONVERSION") { + hex_conversion_enabled = false; + arg_mode = arg_none; + } else if (args[i] == "ENCODING") { + arg_mode = arg_encoding; + } else if (arg_mode == arg_limit_input) { + if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 || + limit_input < 0) { + std::ostringstream e; + e << "STRINGS option LIMIT_INPUT value \"" << args[i] + << "\" is not an unsigned integer."; + this->SetError(e.str()); + return false; + } + arg_mode = arg_none; + } else if (arg_mode == arg_limit_output) { + if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 || + limit_output < 0) { + std::ostringstream e; + e << "STRINGS option LIMIT_OUTPUT value \"" << args[i] + << "\" is not an unsigned integer."; + this->SetError(e.str()); + return false; + } + arg_mode = arg_none; + } else if (arg_mode == arg_limit_count) { + int count; + if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) { + std::ostringstream e; + e << "STRINGS option LIMIT_COUNT value \"" << args[i] + << "\" is not an unsigned integer."; + this->SetError(e.str()); + return false; + } + limit_count = count; + arg_mode = arg_none; + } else if (arg_mode == arg_length_minimum) { + int len; + if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) { + std::ostringstream e; + e << "STRINGS option LENGTH_MINIMUM value \"" << args[i] + << "\" is not an unsigned integer."; + this->SetError(e.str()); + return false; + } + minlen = len; + arg_mode = arg_none; + } else if (arg_mode == arg_length_maximum) { + int len; + if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) { + std::ostringstream e; + e << "STRINGS option LENGTH_MAXIMUM value \"" << args[i] + << "\" is not an unsigned integer."; + this->SetError(e.str()); + return false; + } + maxlen = len; + arg_mode = arg_none; + } else if (arg_mode == arg_regex) { + if (!regex.compile(args[i])) { + std::ostringstream e; + e << "STRINGS option REGEX value \"" << args[i] + << "\" could not be compiled."; + this->SetError(e.str()); + return false; + } + have_regex = true; + arg_mode = arg_none; + } else if (arg_mode == arg_encoding) { + if (args[i] == "UTF-8") { + encoding = encoding_utf8; + } else if (args[i] == "UTF-16LE") { + encoding = encoding_utf16le; + } else if (args[i] == "UTF-16BE") { + encoding = encoding_utf16be; + } else if (args[i] == "UTF-32LE") { + encoding = encoding_utf32le; + } else if (args[i] == "UTF-32BE") { + encoding = encoding_utf32be; + } else { + std::ostringstream e; + e << "STRINGS option ENCODING \"" << args[i] << "\" not recognized."; + this->SetError(e.str()); + return false; + } + arg_mode = arg_none; + } else { + std::ostringstream e; + e << "STRINGS given unknown argument \"" << args[i] << "\""; + this->SetError(e.str()); + return false; + } + } + + if (hex_conversion_enabled) { + // TODO: should work without temp file, but just on a memory buffer + std::string binaryFileName = this->Makefile->GetCurrentBinaryDirectory(); + binaryFileName += "/CMakeFiles"; + binaryFileName += "/FileCommandStringsBinaryFile"; + if (cmHexFileConverter::TryConvert(fileName.c_str(), + binaryFileName.c_str())) { + fileName = binaryFileName; + } + } + +// Open the specified file. +#if defined(_WIN32) || defined(__CYGWIN__) + cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary); +#else + cmsys::ifstream fin(fileName.c_str()); +#endif + if (!fin) { + std::ostringstream e; + e << "STRINGS file \"" << fileName << "\" cannot be read."; + this->SetError(e.str()); + return false; + } + + // If BOM is found and encoding was not specified, use the BOM + int bom_found = cmsys::FStream::ReadBOM(fin); + if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) { + encoding = bom_found; + } + + unsigned int bytes_rem = 0; + if (encoding == encoding_utf16le || encoding == encoding_utf16be) { + bytes_rem = 1; + } + if (encoding == encoding_utf32le || encoding == encoding_utf32be) { + bytes_rem = 3; + } + + // Parse strings out of the file. + int output_size = 0; + std::vector<std::string> strings; + std::string s; + while ((!limit_count || strings.size() < limit_count) && + (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) && + fin) { + std::string current_str; + + int c = fin.get(); + for (unsigned int i = 0; i < bytes_rem; ++i) { + int c1 = fin.get(); + if (!fin) { + fin.putback(static_cast<char>(c1)); + break; + } + c = (c << 8) | c1; + } + if (encoding == encoding_utf16le) { + c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8); + } else if (encoding == encoding_utf32le) { + c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) | + ((c & 0xFF000000) >> 24)); + } + + if (c == '\r') { + // Ignore CR character to make output always have UNIX newlines. + continue; + } + + if (c >= 0 && c <= 0xFF && + (isprint(c) || c == '\t' || (c == '\n' && newline_consume))) { + // This is an ASCII character that may be part of a string. + // Cast added to avoid compiler warning. Cast is ok because + // c is guaranteed to fit in char by the above if... + current_str += static_cast<char>(c); + } else if (encoding == encoding_utf8) { + // Check for UTF-8 encoded string (up to 4 octets) + static const unsigned char utf8_check_table[3][2] = { + { 0xE0, 0xC0 }, + { 0xF0, 0xE0 }, + { 0xF8, 0xF0 }, + }; + + // how many octets are there? + unsigned int num_utf8_bytes = 0; + for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) { + if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) { + num_utf8_bytes = j + 2; + } + } + + // get subsequent octets and check that they are valid + for (unsigned int j = 0; j < num_utf8_bytes; j++) { + if (j != 0) { + c = fin.get(); + if (!fin || (c & 0xC0) != 0x80) { + fin.putback(static_cast<char>(c)); + break; + } + } + current_str += static_cast<char>(c); + } + + // if this was an invalid utf8 sequence, discard the data, and put + // back subsequent characters + if ((current_str.length() != num_utf8_bytes)) { + for (unsigned int j = 0; j < current_str.size() - 1; j++) { + c = current_str[current_str.size() - 1 - j]; + fin.putback(static_cast<char>(c)); + } + current_str.clear(); + } + } + + if (c == '\n' && !newline_consume) { + // The current line has been terminated. Check if the current + // string matches the requirements. The length may now be as + // low as zero since blank lines are allowed. + if (s.length() >= minlen && (!have_regex || regex.find(s))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s.clear(); + break; + } + strings.push_back(s); + } + + // Reset the string to empty. + s.clear(); + } else if (current_str.empty()) { + // A non-string character has been found. Check if the current + // string matches the requirements. We require that the length + // be at least one no matter what the user specified. + if (s.length() >= minlen && !s.empty() && + (!have_regex || regex.find(s))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s.clear(); + break; + } + strings.push_back(s); + } + + // Reset the string to empty. + s.clear(); + } else { + s += current_str; + } + + if (maxlen > 0 && s.size() == maxlen) { + // Terminate a string if the maximum length is reached. + if (s.length() >= minlen && (!have_regex || regex.find(s))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s.clear(); + break; + } + strings.push_back(s); + } + s.clear(); + } + } + + // If there is a non-empty current string we have hit the end of the + // input file or the input size limit. Check if the current string + // matches the requirements. + if ((!limit_count || strings.size() < limit_count) && !s.empty() && + s.length() >= minlen && (!have_regex || regex.find(s))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output < 0 || output_size < limit_output) { + strings.push_back(s); + } + } + + // Encode the result in a CMake list. + const char* sep = ""; + std::string output; + for (std::string const& sr : strings) { + // Separate the strings in the output to make it a list. + output += sep; + sep = ";"; + + // Store the string in the output, but escape semicolons to + // make sure it is a list. + for (char i : sr) { + if (i == ';') { + output += '\\'; + } + output += i; + } + } + + // Save the output in a makefile variable. + this->Makefile->AddDefinition(outVar, output.c_str()); + return true; +} + +bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args, + bool recurse) +{ + // File commands has at least one argument + assert(args.size() > 1); + + std::vector<std::string>::const_iterator i = args.begin(); + + i++; // Get rid of subcommand + + std::string variable = *i; + i++; + cmsys::Glob g; + g.SetRecurse(recurse); + + bool explicitFollowSymlinks = false; + cmPolicies::PolicyStatus status = + this->Makefile->GetPolicyStatus(cmPolicies::CMP0009); + if (recurse) { + switch (status) { + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + g.RecurseThroughSymlinksOff(); + break; + case cmPolicies::OLD: + case cmPolicies::WARN: + g.RecurseThroughSymlinksOn(); + break; + } + } + + std::vector<std::string> files; + bool configureDepends = false; + bool warnConfigureLate = false; + bool warnFollowedSymlinks = false; + const cmake::WorkingMode workingMode = + this->Makefile->GetCMakeInstance()->GetWorkingMode(); + while (i != args.end()) { + if (*i == "LIST_DIRECTORIES") { + ++i; // skip LIST_DIRECTORIES + if (i != args.end()) { + if (cmSystemTools::IsOn(*i)) { + g.SetListDirs(true); + g.SetRecurseListDirs(true); + } else if (cmSystemTools::IsOff(*i)) { + g.SetListDirs(false); + g.SetRecurseListDirs(false); + } else { + this->SetError("LIST_DIRECTORIES missing bool value."); + return false; + } + ++i; + } else { + this->SetError("LIST_DIRECTORIES missing bool value."); + return false; + } + } else if (*i == "FOLLOW_SYMLINKS") { + ++i; // skip FOLLOW_SYMLINKS + if (recurse) { + explicitFollowSymlinks = true; + g.RecurseThroughSymlinksOn(); + if (i == args.end()) { + this->SetError( + "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS."); + return false; + } + } + } else if (*i == "RELATIVE") { + ++i; // skip RELATIVE + if (i == args.end()) { + this->SetError("GLOB requires a directory after the RELATIVE tag."); + return false; + } + g.SetRelative(i->c_str()); + ++i; + if (i == args.end()) { + this->SetError("GLOB requires a glob expression after the directory."); + return false; + } + } else if (*i == "CONFIGURE_DEPENDS") { + // Generated build system depends on glob results + if (!configureDepends && warnConfigureLate) { + this->Makefile->IssueMessage( + MessageType::AUTHOR_WARNING, + "CONFIGURE_DEPENDS flag was given after a glob expression was " + "already evaluated."); + } + if (workingMode != cmake::NORMAL_MODE) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "CONFIGURE_DEPENDS is invalid for script and find package modes."); + return false; + } + configureDepends = true; + ++i; + if (i == args.end()) { + this->SetError( + "GLOB requires a glob expression after CONFIGURE_DEPENDS."); + return false; + } + } else { + std::string expr = *i; + if (!cmsys::SystemTools::FileIsFullPath(*i)) { + expr = this->Makefile->GetCurrentSourceDirectory(); + // Handle script mode + if (!expr.empty()) { + expr += "/" + *i; + } else { + expr = *i; + } + } + + cmsys::Glob::GlobMessages globMessages; + g.FindFiles(expr, &globMessages); + + if (!globMessages.empty()) { + bool shouldExit = false; + for (cmsys::Glob::Message const& globMessage : globMessages) { + if (globMessage.type == cmsys::Glob::cyclicRecursion) { + this->Makefile->IssueMessage( + MessageType::AUTHOR_WARNING, + "Cyclic recursion detected while globbing for '" + *i + "':\n" + + globMessage.content); + } else { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "Error has occurred while globbing for '" + *i + "' - " + + globMessage.content); + shouldExit = true; + } + } + if (shouldExit) { + return false; + } + } + + if (recurse && !explicitFollowSymlinks && + g.GetFollowedSymlinkCount() != 0) { + warnFollowedSymlinks = true; + } + + std::vector<std::string>& foundFiles = g.GetFiles(); + files.insert(files.end(), foundFiles.begin(), foundFiles.end()); + + if (configureDepends) { + std::sort(foundFiles.begin(), foundFiles.end()); + foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()), + foundFiles.end()); + this->Makefile->GetCMakeInstance()->AddGlobCacheEntry( + recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()), + (recurse ? g.GetRecurseThroughSymlinks() : false), + (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable, + this->Makefile->GetBacktrace()); + } else { + warnConfigureLate = true; + } + ++i; + } + } + + switch (status) { + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + // Correct behavior, yay! + break; + case cmPolicies::OLD: + // Probably not really the expected behavior, but the author explicitly + // asked for the old behavior... no warning. + case cmPolicies::WARN: + // Possibly unexpected old behavior *and* we actually traversed + // symlinks without being explicitly asked to: warn the author. + if (warnFollowedSymlinks) { + this->Makefile->IssueMessage( + MessageType::AUTHOR_WARNING, + cmPolicies::GetPolicyWarning(cmPolicies::CMP0009)); + } + break; + } + + std::sort(files.begin(), files.end()); + files.erase(std::unique(files.begin(), files.end()), files.end()); + this->Makefile->AddDefinition(variable, cmJoin(files, ";").c_str()); + return true; +} + +bool cmFileCommand::HandleMakeDirectoryCommand( + std::vector<std::string> const& args) +{ + // File command has at least one argument + assert(args.size() > 1); + + std::string expr; + for (std::string const& arg : + cmMakeRange(args).advance(1)) // Get rid of subcommand + { + const std::string* cdir = &arg; + if (!cmsys::SystemTools::FileIsFullPath(arg)) { + expr = this->Makefile->GetCurrentSourceDirectory(); + expr += "/" + arg; + cdir = &expr; + } + if (!this->Makefile->CanIWriteThisFile(*cdir)) { + std::string e = "attempted to create a directory: " + *cdir + + " into a source directory."; + this->SetError(e); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + if (!cmSystemTools::MakeDirectory(*cdir)) { + std::string error = "problem creating directory: " + *cdir; + this->SetError(error); + return false; + } + } + return true; +} + +bool cmFileCommand::HandleTouchCommand(std::vector<std::string> const& args, + bool create) +{ + // File command has at least one argument + assert(args.size() > 1); + + for (std::string const& arg : + cmMakeRange(args).advance(1)) // Get rid of subcommand + { + std::string tfile = arg; + if (!cmsys::SystemTools::FileIsFullPath(tfile)) { + tfile = this->Makefile->GetCurrentSourceDirectory(); + tfile += "/" + arg; + } + if (!this->Makefile->CanIWriteThisFile(tfile)) { + std::string e = + "attempted to touch a file: " + tfile + " in a source directory."; + this->SetError(e); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + if (!cmSystemTools::Touch(tfile, create)) { + std::string error = "problem touching file: " + tfile; + this->SetError(error); + return false; + } + } + return true; +} + +bool cmFileCommand::HandleDifferentCommand( + std::vector<std::string> const& args) +{ + /* + FILE(DIFFERENT <variable> FILES <lhs> <rhs>) + */ + + // Evaluate arguments. + const char* file_lhs = nullptr; + const char* file_rhs = nullptr; + const char* var = nullptr; + enum Doing + { + DoingNone, + DoingVar, + DoingFileLHS, + DoingFileRHS + }; + Doing doing = DoingVar; + for (unsigned int i = 1; i < args.size(); ++i) { + if (args[i] == "FILES") { + doing = DoingFileLHS; + } else if (doing == DoingVar) { + var = args[i].c_str(); + doing = DoingNone; + } else if (doing == DoingFileLHS) { + file_lhs = args[i].c_str(); + doing = DoingFileRHS; + } else if (doing == DoingFileRHS) { + file_rhs = args[i].c_str(); + doing = DoingNone; + } else { + std::ostringstream e; + e << "DIFFERENT given unknown argument " << args[i]; + this->SetError(e.str()); + return false; + } + } + if (!var) { + this->SetError("DIFFERENT not given result variable name."); + return false; + } + if (!file_lhs || !file_rhs) { + this->SetError("DIFFERENT not given FILES option with two file names."); + return false; + } + + // Compare the files. + const char* result = + cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0"; + this->Makefile->AddDefinition(var, result); + return true; +} + +// File installation helper class. +struct cmFileCopier +{ + cmFileCopier(cmFileCommand* command, const char* name = "COPY") + : FileCommand(command) + , Makefile(command->GetMakefile()) + , Name(name) + , Always(false) + , MatchlessFiles(true) + , FilePermissions(0) + , DirPermissions(0) + , CurrentMatchRule(nullptr) + , UseGivenPermissionsFile(false) + , UseGivenPermissionsDir(false) + , UseSourcePermissions(true) + , Doing(DoingNone) + { + } + virtual ~cmFileCopier() = default; + + bool Run(std::vector<std::string> const& args); + +protected: + cmFileCommand* FileCommand; + cmMakefile* Makefile; + const char* Name; + bool Always; + cmFileTimeComparison FileTimes; + + // Whether to install a file not matching any expression. + bool MatchlessFiles; + + // Permissions for files and directories installed by this object. + mode_t FilePermissions; + mode_t DirPermissions; + + // Properties set by pattern and regex match rules. + struct MatchProperties + { + bool Exclude = false; + mode_t Permissions = 0; + }; + struct MatchRule + { + cmsys::RegularExpression Regex; + MatchProperties Properties; + std::string RegexString; + MatchRule(std::string const& regex) + : Regex(regex) + , RegexString(regex) + { + } + }; + std::vector<MatchRule> MatchRules; + + // Get the properties from rules matching this input file. + MatchProperties CollectMatchProperties(const std::string& file) + { +// Match rules are case-insensitive on some platforms. +#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__) + const std::string file_to_match = cmSystemTools::LowerCase(file); +#else + const std::string& file_to_match = file; +#endif + + // Collect properties from all matching rules. + bool matched = false; + MatchProperties result; + for (MatchRule& mr : this->MatchRules) { + if (mr.Regex.find(file_to_match)) { + matched = true; + result.Exclude |= mr.Properties.Exclude; + result.Permissions |= mr.Properties.Permissions; + } + } + if (!matched && !this->MatchlessFiles) { + result.Exclude = !cmSystemTools::FileIsDirectory(file); + } + return result; + } + + bool SetPermissions(const std::string& toFile, mode_t permissions) + { + if (permissions) { +#ifdef WIN32 + if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) { + // Store the mode in an NTFS alternate stream. + std::string mode_t_adt_filename = toFile + ":cmake_mode_t"; + + // Writing to an NTFS alternate stream changes the modification + // time, so we need to save and restore its original value. + cmSystemToolsFileTime* file_time_orig = cmSystemTools::FileTimeNew(); + cmSystemTools::FileTimeGet(toFile, file_time_orig); + + cmsys::ofstream permissionStream(mode_t_adt_filename.c_str()); + + if (permissionStream) { + permissionStream << std::oct << permissions << std::endl; + } + + permissionStream.close(); + + cmSystemTools::FileTimeSet(toFile, file_time_orig); + + cmSystemTools::FileTimeDelete(file_time_orig); + } +#endif + + if (!cmSystemTools::SetPermissions(toFile, permissions)) { + std::ostringstream e; + e << this->Name << " cannot set permissions on \"" << toFile << "\""; + this->FileCommand->SetError(e.str()); + return false; + } + } + return true; + } + + // Translate an argument to a permissions bit. + bool CheckPermissions(std::string const& arg, mode_t& permissions) + { + if (!cmFSPermissions::stringToModeT(arg, permissions)) { + std::ostringstream e; + e << this->Name << " given invalid permission \"" << arg << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + return true; + } + + bool InstallSymlink(const std::string& fromFile, const std::string& toFile); + bool InstallFile(const std::string& fromFile, const std::string& toFile, + MatchProperties match_properties); + bool InstallDirectory(const std::string& source, + const std::string& destination, + MatchProperties match_properties); + virtual bool Install(const std::string& fromFile, const std::string& toFile); + virtual std::string const& ToName(std::string const& fromName) + { + return fromName; + } + + enum Type + { + TypeFile, + TypeDir, + TypeLink + }; + virtual void ReportCopy(const std::string&, Type, bool) {} + virtual bool ReportMissing(const std::string& fromFile) + { + // The input file does not exist and installation is not optional. + std::ostringstream e; + e << this->Name << " cannot find \"" << fromFile << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + + MatchRule* CurrentMatchRule; + bool UseGivenPermissionsFile; + bool UseGivenPermissionsDir; + bool UseSourcePermissions; + std::string Destination; + std::string FilesFromDir; + std::vector<std::string> Files; + int Doing; + + virtual bool Parse(std::vector<std::string> const& args); + enum + { + DoingNone, + DoingError, + DoingDestination, + DoingFilesFromDir, + DoingFiles, + DoingPattern, + DoingRegex, + DoingPermissionsFile, + DoingPermissionsDir, + DoingPermissionsMatch, + DoingLast1 + }; + virtual bool CheckKeyword(std::string const& arg); + virtual bool CheckValue(std::string const& arg); + + void NotBeforeMatch(std::string const& arg) + { + std::ostringstream e; + e << "option " << arg << " may not appear before PATTERN or REGEX."; + this->FileCommand->SetError(e.str()); + this->Doing = DoingError; + } + void NotAfterMatch(std::string const& arg) + { + std::ostringstream e; + e << "option " << arg << " may not appear after PATTERN or REGEX."; + this->FileCommand->SetError(e.str()); + this->Doing = DoingError; + } + virtual void DefaultFilePermissions() + { + // Use read/write permissions. + this->FilePermissions = 0; + this->FilePermissions |= mode_owner_read; + this->FilePermissions |= mode_owner_write; + this->FilePermissions |= mode_group_read; + this->FilePermissions |= mode_world_read; + } + virtual void DefaultDirectoryPermissions() + { + // Use read/write/executable permissions. + this->DirPermissions = 0; + this->DirPermissions |= mode_owner_read; + this->DirPermissions |= mode_owner_write; + this->DirPermissions |= mode_owner_execute; + this->DirPermissions |= mode_group_read; + this->DirPermissions |= mode_group_execute; + this->DirPermissions |= mode_world_read; + this->DirPermissions |= mode_world_execute; + } + + bool GetDefaultDirectoryPermissions(mode_t** mode) + { + // check if default dir creation permissions were set + const char* default_dir_install_permissions = + this->Makefile->GetDefinition( + "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS"); + if (default_dir_install_permissions && *default_dir_install_permissions) { + std::vector<std::string> items; + cmSystemTools::ExpandListArgument(default_dir_install_permissions, + items); + for (const auto& arg : items) { + if (!this->CheckPermissions(arg, **mode)) { + std::ostringstream e; + e << this->FileCommand->GetError() + << " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS " + "variable."; + this->FileCommand->SetError(e.str()); + return false; + } + } + } else { + *mode = nullptr; + } + + return true; + } +}; + +bool cmFileCopier::Parse(std::vector<std::string> const& args) +{ + this->Doing = DoingFiles; + for (unsigned int i = 1; i < args.size(); ++i) { + // Check this argument. + if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) { + std::ostringstream e; + e << "called with unknown argument \"" << args[i] << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + + // Quit if an argument is invalid. + if (this->Doing == DoingError) { + return false; + } + } + + // Require a destination. + if (this->Destination.empty()) { + std::ostringstream e; + e << this->Name << " given no DESTINATION"; + this->FileCommand->SetError(e.str()); + return false; + } + + // If file permissions were not specified set default permissions. + if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) { + this->DefaultFilePermissions(); + } + + // If directory permissions were not specified set default permissions. + if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) { + this->DefaultDirectoryPermissions(); + } + + return true; +} + +bool cmFileCopier::CheckKeyword(std::string const& arg) +{ + if (arg == "DESTINATION") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingDestination; + } + } else if (arg == "FILES_FROM_DIR") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingFilesFromDir; + } + } else if (arg == "PATTERN") { + this->Doing = DoingPattern; + } else if (arg == "REGEX") { + this->Doing = DoingRegex; + } else if (arg == "EXCLUDE") { + // Add this property to the current match rule. + if (this->CurrentMatchRule) { + this->CurrentMatchRule->Properties.Exclude = true; + this->Doing = DoingNone; + } else { + this->NotBeforeMatch(arg); + } + } else if (arg == "PERMISSIONS") { + if (this->CurrentMatchRule) { + this->Doing = DoingPermissionsMatch; + } else { + this->NotBeforeMatch(arg); + } + } else if (arg == "FILE_PERMISSIONS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingPermissionsFile; + this->UseGivenPermissionsFile = true; + } + } else if (arg == "DIRECTORY_PERMISSIONS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingPermissionsDir; + this->UseGivenPermissionsDir = true; + } + } else if (arg == "USE_SOURCE_PERMISSIONS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->UseSourcePermissions = true; + } + } else if (arg == "NO_SOURCE_PERMISSIONS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->UseSourcePermissions = false; + } + } else if (arg == "FILES_MATCHING") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->MatchlessFiles = false; + } + } else { + return false; + } + return true; +} + +bool cmFileCopier::CheckValue(std::string const& arg) +{ + switch (this->Doing) { + case DoingFiles: + this->Files.push_back(arg); + break; + case DoingDestination: + if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) { + this->Destination = arg; + } else { + this->Destination = this->Makefile->GetCurrentBinaryDirectory(); + this->Destination += "/" + arg; + } + this->Doing = DoingNone; + break; + case DoingFilesFromDir: + if (cmSystemTools::FileIsFullPath(arg)) { + this->FilesFromDir = arg; + } else { + this->FilesFromDir = this->Makefile->GetCurrentSourceDirectory(); + this->FilesFromDir += "/" + arg; + } + cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir); + this->Doing = DoingNone; + break; + case DoingPattern: { + // Convert the pattern to a regular expression. Require a + // leading slash and trailing end-of-string in the matched + // string to make sure the pattern matches only whole file + // names. + std::string regex = "/"; + regex += cmsys::Glob::PatternToRegex(arg, false); + regex += "$"; + this->MatchRules.emplace_back(regex); + this->CurrentMatchRule = &*(this->MatchRules.end() - 1); + if (this->CurrentMatchRule->Regex.is_valid()) { + this->Doing = DoingNone; + } else { + std::ostringstream e; + e << "could not compile PATTERN \"" << arg << "\"."; + this->FileCommand->SetError(e.str()); + this->Doing = DoingError; + } + } break; + case DoingRegex: + this->MatchRules.emplace_back(arg); + this->CurrentMatchRule = &*(this->MatchRules.end() - 1); + if (this->CurrentMatchRule->Regex.is_valid()) { + this->Doing = DoingNone; + } else { + std::ostringstream e; + e << "could not compile REGEX \"" << arg << "\"."; + this->FileCommand->SetError(e.str()); + this->Doing = DoingError; + } + break; + case DoingPermissionsFile: + if (!this->CheckPermissions(arg, this->FilePermissions)) { + this->Doing = DoingError; + } + break; + case DoingPermissionsDir: + if (!this->CheckPermissions(arg, this->DirPermissions)) { + this->Doing = DoingError; + } + break; + case DoingPermissionsMatch: + if (!this->CheckPermissions( + arg, this->CurrentMatchRule->Properties.Permissions)) { + this->Doing = DoingError; + } + break; + default: + return false; + } + return true; +} + +bool cmFileCopier::Run(std::vector<std::string> const& args) +{ + if (!this->Parse(args)) { + return false; + } + + for (std::string const& f : this->Files) { + std::string file; + if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) { + if (!this->FilesFromDir.empty()) { + file = this->FilesFromDir; + } else { + file = this->Makefile->GetCurrentSourceDirectory(); + } + file += "/"; + file += f; + } else if (!this->FilesFromDir.empty()) { + this->FileCommand->SetError("option FILES_FROM_DIR requires all files " + "to be specified as relative paths."); + return false; + } else { + file = f; + } + + // Split the input file into its directory and name components. + std::vector<std::string> fromPathComponents; + cmSystemTools::SplitPath(file, fromPathComponents); + std::string fromName = *(fromPathComponents.end() - 1); + std::string fromDir = cmSystemTools::JoinPath( + fromPathComponents.begin(), fromPathComponents.end() - 1); + + // Compute the full path to the destination file. + std::string toFile = this->Destination; + if (!this->FilesFromDir.empty()) { + std::string dir = cmSystemTools::GetFilenamePath(f); + if (!dir.empty()) { + toFile += "/"; + toFile += dir; + } + } + std::string const& toName = this->ToName(fromName); + if (!toName.empty()) { + toFile += "/"; + toFile += toName; + } + + // Construct the full path to the source file. The file name may + // have been changed above. + std::string fromFile = fromDir; + if (!fromName.empty()) { + fromFile += "/"; + fromFile += fromName; + } + + if (!this->Install(fromFile, toFile)) { + return false; + } + } + return true; +} + +bool cmFileCopier::Install(const std::string& fromFile, + const std::string& toFile) +{ + if (fromFile.empty()) { + std::ostringstream e; + e << "INSTALL encountered an empty string input file name."; + this->FileCommand->SetError(e.str()); + return false; + } + + // Collect any properties matching this file name. + MatchProperties match_properties = this->CollectMatchProperties(fromFile); + + // Skip the file if it is excluded. + if (match_properties.Exclude) { + return true; + } + + if (cmSystemTools::SameFile(fromFile, toFile)) { + return true; + } + if (cmSystemTools::FileIsSymlink(fromFile)) { + return this->InstallSymlink(fromFile, toFile); + } + if (cmSystemTools::FileIsDirectory(fromFile)) { + return this->InstallDirectory(fromFile, toFile, match_properties); + } + if (cmSystemTools::FileExists(fromFile)) { + return this->InstallFile(fromFile, toFile, match_properties); + } + return this->ReportMissing(fromFile); +} + +bool cmFileCopier::InstallSymlink(const std::string& fromFile, + const std::string& toFile) +{ + // Read the original symlink. + std::string symlinkTarget; + if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) { + std::ostringstream e; + e << this->Name << " cannot read symlink \"" << fromFile + << "\" to duplicate at \"" << toFile << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + + // Compare the symlink value to that at the destination if not + // always installing. + bool copy = true; + if (!this->Always) { + std::string oldSymlinkTarget; + if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) { + if (symlinkTarget == oldSymlinkTarget) { + copy = false; + } + } + } + + // Inform the user about this file installation. + this->ReportCopy(toFile, TypeLink, copy); + + if (copy) { + // Remove the destination file so we can always create the symlink. + cmSystemTools::RemoveFile(toFile); + + // Create destination directory if it doesn't exist + cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile)); + + // Create the symlink. + if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) { + std::ostringstream e; + e << this->Name << " cannot duplicate symlink \"" << fromFile + << "\" at \"" << toFile << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + } + + return true; +} + +bool cmFileCopier::InstallFile(const std::string& fromFile, + const std::string& toFile, + MatchProperties match_properties) +{ + // Determine whether we will copy the file. + bool copy = true; + if (!this->Always) { + // If both files exist with the same time do not copy. + if (!this->FileTimes.FileTimesDiffer(fromFile, toFile)) { + copy = false; + } + } + + // Inform the user about this file installation. + this->ReportCopy(toFile, TypeFile, copy); + + // Copy the file. + if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) { + std::ostringstream e; + e << this->Name << " cannot copy file \"" << fromFile << "\" to \"" + << toFile << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + + // Set the file modification time of the destination file. + if (copy && !this->Always) { + // Add write permission so we can set the file time. + // Permissions are set unconditionally below anyway. + mode_t perm = 0; + if (cmSystemTools::GetPermissions(toFile, perm)) { + cmSystemTools::SetPermissions(toFile, perm | mode_owner_write); + } + if (!cmSystemTools::CopyFileTime(fromFile, toFile)) { + std::ostringstream e; + e << this->Name << " cannot set modification time on \"" << toFile + << "\""; + this->FileCommand->SetError(e.str()); + return false; + } + } + + // Set permissions of the destination file. + mode_t permissions = + (match_properties.Permissions ? match_properties.Permissions + : this->FilePermissions); + if (!permissions) { + // No permissions were explicitly provided but the user requested + // that the source file permissions be used. + cmSystemTools::GetPermissions(fromFile, permissions); + } + return this->SetPermissions(toFile, permissions); +} + +bool cmFileCopier::InstallDirectory(const std::string& source, + const std::string& destination, + MatchProperties match_properties) +{ + // Inform the user about this directory installation. + this->ReportCopy(destination, TypeDir, + !cmSystemTools::FileIsDirectory(destination)); + + // check if default dir creation permissions were set + mode_t default_dir_mode_v = 0; + mode_t* default_dir_mode = &default_dir_mode_v; + if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) { + return false; + } + + // Make sure the destination directory exists. + if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) { + std::ostringstream e; + e << this->Name << " cannot make directory \"" << destination + << "\": " << cmSystemTools::GetLastSystemError(); + this->FileCommand->SetError(e.str()); + return false; + } + + // Compute the requested permissions for the destination directory. + mode_t permissions = + (match_properties.Permissions ? match_properties.Permissions + : this->DirPermissions); + if (!permissions) { + // No permissions were explicitly provided but the user requested + // that the source directory permissions be used. + cmSystemTools::GetPermissions(source, permissions); + } + + // Compute the set of permissions required on this directory to + // recursively install files and subdirectories safely. + mode_t required_permissions = + mode_owner_read | mode_owner_write | mode_owner_execute; + + // If the required permissions are specified it is safe to set the + // final permissions now. Otherwise we must add the required + // permissions temporarily during file installation. + mode_t permissions_before = 0; + mode_t permissions_after = 0; + if ((permissions & required_permissions) == required_permissions) { + permissions_before = permissions; + } else { + permissions_before = permissions | required_permissions; + permissions_after = permissions; + } + + // Set the required permissions of the destination directory. + if (!this->SetPermissions(destination, permissions_before)) { + return false; + } + + // Load the directory contents to traverse it recursively. + cmsys::Directory dir; + if (!source.empty()) { + dir.Load(source); + } + unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles()); + for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) { + if (!(strcmp(dir.GetFile(fileNum), ".") == 0 || + strcmp(dir.GetFile(fileNum), "..") == 0)) { + std::string fromPath = source; + fromPath += "/"; + fromPath += dir.GetFile(fileNum); + std::string toPath = destination; + toPath += "/"; + toPath += dir.GetFile(fileNum); + if (!this->Install(fromPath, toPath)) { + return false; + } + } + } + + // Set the requested permissions of the destination directory. + return this->SetPermissions(destination, permissions_after); +} + +bool cmFileCommand::HandleCopyCommand(std::vector<std::string> const& args) +{ + cmFileCopier copier(this); + return copier.Run(args); +} + +struct cmFileInstaller : public cmFileCopier +{ + cmFileInstaller(cmFileCommand* command) + : cmFileCopier(command, "INSTALL") + , InstallType(cmInstallType_FILES) + , Optional(false) + , MessageAlways(false) + , MessageLazy(false) + , MessageNever(false) + , DestDirLength(0) + { + // Installation does not use source permissions by default. + this->UseSourcePermissions = false; + // Check whether to copy files always or only if they have changed. + std::string install_always; + if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) { + this->Always = cmSystemTools::IsOn(install_always); + } + // Get the current manifest. + this->Manifest = + this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES"); + } + ~cmFileInstaller() override + { + // Save the updated install manifest. + this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", + this->Manifest.c_str()); + } + +protected: + cmInstallType InstallType; + bool Optional; + bool MessageAlways; + bool MessageLazy; + bool MessageNever; + int DestDirLength; + std::string Rename; + + std::string Manifest; + void ManifestAppend(std::string const& file) + { + if (!this->Manifest.empty()) { + this->Manifest += ";"; + } + this->Manifest += file.substr(this->DestDirLength); + } + + std::string const& ToName(std::string const& fromName) override + { + return this->Rename.empty() ? fromName : this->Rename; + } + + void ReportCopy(const std::string& toFile, Type type, bool copy) override + { + if (!this->MessageNever && (copy || !this->MessageLazy)) { + std::string message = (copy ? "Installing: " : "Up-to-date: "); + message += toFile; + this->Makefile->DisplayStatus(message, -1); + } + if (type != TypeDir) { + // Add the file to the manifest. + this->ManifestAppend(toFile); + } + } + bool ReportMissing(const std::string& fromFile) override + { + return (this->Optional || this->cmFileCopier::ReportMissing(fromFile)); + } + bool Install(const std::string& fromFile, const std::string& toFile) override + { + // Support installing from empty source to make a directory. + if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) { + return this->InstallDirectory(fromFile, toFile, MatchProperties()); + } + return this->cmFileCopier::Install(fromFile, toFile); + } + + bool Parse(std::vector<std::string> const& args) override; + enum + { + DoingType = DoingLast1, + DoingRename, + DoingLast2 + }; + bool CheckKeyword(std::string const& arg) override; + bool CheckValue(std::string const& arg) override; + void DefaultFilePermissions() override + { + this->cmFileCopier::DefaultFilePermissions(); + // Add execute permissions based on the target type. + switch (this->InstallType) { + case cmInstallType_SHARED_LIBRARY: + case cmInstallType_MODULE_LIBRARY: + if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) { + break; + } + CM_FALLTHROUGH; + case cmInstallType_EXECUTABLE: + case cmInstallType_PROGRAMS: + this->FilePermissions |= mode_owner_execute; + this->FilePermissions |= mode_group_execute; + this->FilePermissions |= mode_world_execute; + break; + default: + break; + } + } + bool GetTargetTypeFromString(const std::string& stype); + bool HandleInstallDestination(); +}; + +bool cmFileInstaller::Parse(std::vector<std::string> const& args) +{ + if (!this->cmFileCopier::Parse(args)) { + return false; + } + + if (!this->Rename.empty()) { + if (!this->FilesFromDir.empty()) { + this->FileCommand->SetError("INSTALL option RENAME may not be " + "combined with FILES_FROM_DIR."); + return false; + } + if (this->InstallType != cmInstallType_FILES && + this->InstallType != cmInstallType_PROGRAMS) { + this->FileCommand->SetError("INSTALL option RENAME may be used " + "only with FILES or PROGRAMS."); + return false; + } + if (this->Files.size() > 1) { + this->FileCommand->SetError("INSTALL option RENAME may be used " + "only with one file."); + return false; + } + } + + if (!this->HandleInstallDestination()) { + return false; + } + + if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) + + (this->MessageNever ? 1 : 0)) > 1) { + this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, " + "MESSAGE_LAZY, and MESSAGE_NEVER " + "are mutually exclusive."); + return false; + } + + return true; +} + +bool cmFileInstaller::CheckKeyword(std::string const& arg) +{ + if (arg == "TYPE") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingType; + } + } else if (arg == "FILES") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingFiles; + } + } else if (arg == "RENAME") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingRename; + } + } else if (arg == "OPTIONAL") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->Optional = true; + } + } else if (arg == "MESSAGE_ALWAYS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->MessageAlways = true; + } + } else if (arg == "MESSAGE_LAZY") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->MessageLazy = true; + } + } else if (arg == "MESSAGE_NEVER") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + this->Doing = DoingNone; + this->MessageNever = true; + } + } else if (arg == "PERMISSIONS") { + if (this->CurrentMatchRule) { + this->Doing = DoingPermissionsMatch; + } else { + // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS + this->Doing = DoingPermissionsFile; + this->UseGivenPermissionsFile = true; + } + } else if (arg == "DIR_PERMISSIONS") { + if (this->CurrentMatchRule) { + this->NotAfterMatch(arg); + } else { + // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS + this->Doing = DoingPermissionsDir; + this->UseGivenPermissionsDir = true; + } + } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" || + arg == "PROPERTIES") { + std::ostringstream e; + e << "INSTALL called with old-style " << arg << " argument. " + << "This script was generated with an older version of CMake. " + << "Re-run this cmake version on your build tree."; + this->FileCommand->SetError(e.str()); + this->Doing = DoingError; + } else { + return this->cmFileCopier::CheckKeyword(arg); + } + return true; +} + +bool cmFileInstaller::CheckValue(std::string const& arg) +{ + switch (this->Doing) { + case DoingType: + if (!this->GetTargetTypeFromString(arg)) { + this->Doing = DoingError; + } + break; + case DoingRename: + this->Rename = arg; + break; + default: + return this->cmFileCopier::CheckValue(arg); + } + return true; +} + +bool cmFileInstaller::GetTargetTypeFromString(const std::string& stype) +{ + if (stype == "EXECUTABLE") { + this->InstallType = cmInstallType_EXECUTABLE; + } else if (stype == "FILE") { + this->InstallType = cmInstallType_FILES; + } else if (stype == "PROGRAM") { + this->InstallType = cmInstallType_PROGRAMS; + } else if (stype == "STATIC_LIBRARY") { + this->InstallType = cmInstallType_STATIC_LIBRARY; + } else if (stype == "SHARED_LIBRARY") { + this->InstallType = cmInstallType_SHARED_LIBRARY; + } else if (stype == "MODULE") { + this->InstallType = cmInstallType_MODULE_LIBRARY; + } else if (stype == "DIRECTORY") { + this->InstallType = cmInstallType_DIRECTORY; + } else { + std::ostringstream e; + e << "Option TYPE given unknown value \"" << stype << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + return true; +} + +bool cmFileInstaller::HandleInstallDestination() +{ + std::string& destination = this->Destination; + + // allow for / to be a valid destination + if (destination.size() < 2 && destination != "/") { + this->FileCommand->SetError("called with inappropriate arguments. " + "No DESTINATION provided or ."); + return false; + } + + std::string sdestdir; + if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) { + cmSystemTools::ConvertToUnixSlashes(sdestdir); + char ch1 = destination[0]; + char ch2 = destination[1]; + char ch3 = 0; + if (destination.size() > 2) { + ch3 = destination[2]; + } + int skip = 0; + if (ch1 != '/') { + int relative = 0; + if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) && + ch2 == ':') { + // Assume windows + // let's do some destdir magic: + skip = 2; + if (ch3 != '/') { + relative = 1; + } + } else { + relative = 1; + } + if (relative) { + // This is relative path on unix or windows. Since we are doing + // destdir, this case does not make sense. + this->FileCommand->SetError( + "called with relative DESTINATION. This " + "does not make sense when using DESTDIR. Specify " + "absolute path or remove DESTDIR environment variable."); + return false; + } + } else { + if (ch2 == '/') { + // looks like a network path. + std::string message = + "called with network path DESTINATION. This " + "does not make sense when using DESTDIR. Specify local " + "absolute path or remove DESTDIR environment variable." + "\nDESTINATION=\n"; + message += destination; + this->FileCommand->SetError(message); + return false; + } + } + destination = sdestdir + (destination.c_str() + skip); + this->DestDirLength = int(sdestdir.size()); + } + + // check if default dir creation permissions were set + mode_t default_dir_mode_v = 0; + mode_t* default_dir_mode = &default_dir_mode_v; + if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) { + return false; + } + + if (this->InstallType != cmInstallType_DIRECTORY) { + if (!cmSystemTools::FileExists(destination)) { + if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) { + std::string errstring = "cannot create directory: " + destination + + ". Maybe need administrative privileges."; + this->FileCommand->SetError(errstring); + return false; + } + } + if (!cmSystemTools::FileIsDirectory(destination)) { + std::string errstring = + "INSTALL destination: " + destination + " is not a directory."; + this->FileCommand->SetError(errstring); + return false; + } + } + return true; +} + +bool cmFileCommand::HandleRPathChangeCommand( + std::vector<std::string> const& args) +{ + // Evaluate arguments. + std::string file; + const char* oldRPath = nullptr; + const char* newRPath = nullptr; + enum Doing + { + DoingNone, + DoingFile, + DoingOld, + DoingNew + }; + Doing doing = DoingNone; + for (unsigned int i = 1; i < args.size(); ++i) { + if (args[i] == "OLD_RPATH") { + doing = DoingOld; + } else if (args[i] == "NEW_RPATH") { + doing = DoingNew; + } else if (args[i] == "FILE") { + doing = DoingFile; + } else if (doing == DoingFile) { + file = args[i]; + doing = DoingNone; + } else if (doing == DoingOld) { + oldRPath = args[i].c_str(); + doing = DoingNone; + } else if (doing == DoingNew) { + newRPath = args[i].c_str(); + doing = DoingNone; + } else { + std::ostringstream e; + e << "RPATH_CHANGE given unknown argument " << args[i]; + this->SetError(e.str()); + return false; + } + } + if (file.empty()) { + this->SetError("RPATH_CHANGE not given FILE option."); + return false; + } + if (!oldRPath) { + this->SetError("RPATH_CHANGE not given OLD_RPATH option."); + return false; + } + if (!newRPath) { + this->SetError("RPATH_CHANGE not given NEW_RPATH option."); + return false; + } + if (!cmSystemTools::FileExists(file, true)) { + std::ostringstream e; + e << "RPATH_CHANGE given FILE \"" << file << "\" that does not exist."; + this->SetError(e.str()); + return false; + } + bool success = true; + cmSystemToolsFileTime* ft = cmSystemTools::FileTimeNew(); + bool have_ft = cmSystemTools::FileTimeGet(file, ft); + std::string emsg; + bool changed; + if (!cmSystemTools::ChangeRPath(file, oldRPath, newRPath, &emsg, &changed)) { + std::ostringstream e; + /* clang-format off */ + e << "RPATH_CHANGE could not write new RPATH:\n" + << " " << newRPath << "\n" + << "to the file:\n" + << " " << file << "\n" + << emsg; + /* clang-format on */ + this->SetError(e.str()); + success = false; + } + if (success) { + if (changed) { + std::string message = "Set runtime path of \""; + message += file; + message += "\" to \""; + message += newRPath; + message += "\""; + this->Makefile->DisplayStatus(message, -1); + } + if (have_ft) { + cmSystemTools::FileTimeSet(file, ft); + } + } + cmSystemTools::FileTimeDelete(ft); + return success; +} + +bool cmFileCommand::HandleRPathRemoveCommand( + std::vector<std::string> const& args) +{ + // Evaluate arguments. + std::string file; + enum Doing + { + DoingNone, + DoingFile + }; + Doing doing = DoingNone; + for (unsigned int i = 1; i < args.size(); ++i) { + if (args[i] == "FILE") { + doing = DoingFile; + } else if (doing == DoingFile) { + file = args[i]; + doing = DoingNone; + } else { + std::ostringstream e; + e << "RPATH_REMOVE given unknown argument " << args[i]; + this->SetError(e.str()); + return false; + } + } + if (file.empty()) { + this->SetError("RPATH_REMOVE not given FILE option."); + return false; + } + if (!cmSystemTools::FileExists(file, true)) { + std::ostringstream e; + e << "RPATH_REMOVE given FILE \"" << file << "\" that does not exist."; + this->SetError(e.str()); + return false; + } + bool success = true; + cmSystemToolsFileTime* ft = cmSystemTools::FileTimeNew(); + bool have_ft = cmSystemTools::FileTimeGet(file, ft); + std::string emsg; + bool removed; + if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) { + std::ostringstream e; + /* clang-format off */ + e << "RPATH_REMOVE could not remove RPATH from file:\n" + << " " << file << "\n" + << emsg; + /* clang-format on */ + this->SetError(e.str()); + success = false; + } + if (success) { + if (removed) { + std::string message = "Removed runtime path from \""; + message += file; + message += "\""; + this->Makefile->DisplayStatus(message, -1); + } + if (have_ft) { + cmSystemTools::FileTimeSet(file, ft); + } + } + cmSystemTools::FileTimeDelete(ft); + return success; +} + +bool cmFileCommand::HandleRPathCheckCommand( + std::vector<std::string> const& args) +{ + // Evaluate arguments. + std::string file; + const char* rpath = nullptr; + enum Doing + { + DoingNone, + DoingFile, + DoingRPath + }; + Doing doing = DoingNone; + for (unsigned int i = 1; i < args.size(); ++i) { + if (args[i] == "RPATH") { + doing = DoingRPath; + } else if (args[i] == "FILE") { + doing = DoingFile; + } else if (doing == DoingFile) { + file = args[i]; + doing = DoingNone; + } else if (doing == DoingRPath) { + rpath = args[i].c_str(); + doing = DoingNone; + } else { + std::ostringstream e; + e << "RPATH_CHECK given unknown argument " << args[i]; + this->SetError(e.str()); + return false; + } + } + if (file.empty()) { + this->SetError("RPATH_CHECK not given FILE option."); + return false; + } + if (!rpath) { + this->SetError("RPATH_CHECK not given RPATH option."); + return false; + } + + // If the file exists but does not have the desired RPath then + // delete it. This is used during installation to re-install a file + // if its RPath will change. + if (cmSystemTools::FileExists(file, true) && + !cmSystemTools::CheckRPath(file, rpath)) { + cmSystemTools::RemoveFile(file); + } + + return true; +} + +bool cmFileCommand::HandleReadElfCommand(std::vector<std::string> const& args) +{ + if (args.size() < 4) { + this->SetError("READ_ELF must be called with at least three additional " + "arguments."); + return false; + } + + cmCommandArgumentsHelper argHelper; + cmCommandArgumentGroup group; + + cmCAString readArg(&argHelper, "READ_ELF"); + cmCAString fileNameArg(&argHelper, nullptr); + + cmCAString rpathArg(&argHelper, "RPATH", &group); + cmCAString runpathArg(&argHelper, "RUNPATH", &group); + cmCAString errorArg(&argHelper, "CAPTURE_ERROR", &group); + + readArg.Follows(nullptr); + fileNameArg.Follows(&readArg); + group.Follows(&fileNameArg); + argHelper.Parse(&args, nullptr); + + if (!cmSystemTools::FileExists(fileNameArg.GetString(), true)) { + std::ostringstream e; + e << "READ_ELF given FILE \"" << fileNameArg.GetString() + << "\" that does not exist."; + this->SetError(e.str()); + return false; + } + +#if defined(CMAKE_USE_ELF_PARSER) + cmELF elf(fileNameArg.GetCString()); + + if (!rpathArg.GetString().empty()) { + if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) { + std::string rpath(se_rpath->Value); + std::replace(rpath.begin(), rpath.end(), ':', ';'); + this->Makefile->AddDefinition(rpathArg.GetString(), rpath.c_str()); + } + } + if (!runpathArg.GetString().empty()) { + if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) { + std::string runpath(se_runpath->Value); + std::replace(runpath.begin(), runpath.end(), ':', ';'); + this->Makefile->AddDefinition(runpathArg.GetString(), runpath.c_str()); + } + } + + return true; +#else + std::string error = "ELF parser not available on this platform."; + if (errorArg.GetString().empty()) { + this->SetError(error); + return false; + } + this->Makefile->AddDefinition(errorArg.GetString(), error.c_str()); + return true; +#endif +} + +bool cmFileCommand::HandleInstallCommand(std::vector<std::string> const& args) +{ + cmFileInstaller installer(this); + return installer.Run(args); +} + +bool cmFileCommand::HandleRelativePathCommand( + std::vector<std::string> const& args) +{ + if (args.size() != 4) { + this->SetError("RELATIVE_PATH called with incorrect number of arguments"); + return false; + } + + const std::string& outVar = args[1]; + const std::string& directoryName = args[2]; + const std::string& fileName = args[3]; + + if (!cmSystemTools::FileIsFullPath(directoryName)) { + std::string errstring = + "RELATIVE_PATH must be passed a full path to the directory: " + + directoryName; + this->SetError(errstring); + return false; + } + if (!cmSystemTools::FileIsFullPath(fileName)) { + std::string errstring = + "RELATIVE_PATH must be passed a full path to the file: " + fileName; + this->SetError(errstring); + return false; + } + + std::string res = cmSystemTools::RelativePath(directoryName, fileName); + this->Makefile->AddDefinition(outVar, res.c_str()); + return true; +} + +bool cmFileCommand::HandleRename(std::vector<std::string> const& args) +{ + if (args.size() != 3) { + this->SetError("RENAME given incorrect number of arguments."); + return false; + } + + // Compute full path for old and new names. + std::string oldname = args[1]; + if (!cmsys::SystemTools::FileIsFullPath(oldname)) { + oldname = this->Makefile->GetCurrentSourceDirectory(); + oldname += "/" + args[1]; + } + std::string newname = args[2]; + if (!cmsys::SystemTools::FileIsFullPath(newname)) { + newname = this->Makefile->GetCurrentSourceDirectory(); + newname += "/" + args[2]; + } + + if (!cmSystemTools::RenameFile(oldname, newname)) { + std::string err = cmSystemTools::GetLastSystemError(); + std::ostringstream e; + /* clang-format off */ + e << "RENAME failed to rename\n" + << " " << oldname << "\n" + << "to\n" + << " " << newname << "\n" + << "because: " << err << "\n"; + /* clang-format on */ + this->SetError(e.str()); + return false; + } + return true; +} + +bool cmFileCommand::HandleRemove(std::vector<std::string> const& args, + bool recurse) +{ + + std::string message; + + for (std::string const& arg : + cmMakeRange(args).advance(1)) // Get rid of subcommand + { + std::string fileName = arg; + if (!cmsys::SystemTools::FileIsFullPath(fileName)) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + arg; + } + + if (cmSystemTools::FileIsDirectory(fileName) && + !cmSystemTools::FileIsSymlink(fileName) && recurse) { + cmSystemTools::RemoveADirectory(fileName); + } else { + cmSystemTools::RemoveFile(fileName); + } + } + return true; +} + +bool cmFileCommand::HandleCMakePathCommand( + std::vector<std::string> const& args, bool nativePath) +{ + std::vector<std::string>::const_iterator i = args.begin(); + if (args.size() != 3) { + this->SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be " + "called with exactly three arguments."); + return false; + } + i++; // Get rid of subcommand +#if defined(_WIN32) && !defined(__CYGWIN__) + char pathSep = ';'; +#else + char pathSep = ':'; +#endif + std::vector<std::string> path = cmSystemTools::SplitString(*i, pathSep); + i++; + const char* var = i->c_str(); + std::string value; + for (std::vector<std::string>::iterator j = path.begin(); j != path.end(); + ++j) { + if (j != path.begin()) { + value += ";"; + } + if (!nativePath) { + cmSystemTools::ConvertToUnixSlashes(*j); + } else { + *j = cmSystemTools::ConvertToOutputPath(*j); + // remove double quotes in the path + std::string& s = *j; + + if (s.size() > 1 && s.front() == '\"' && s.back() == '\"') { + s = s.substr(1, s.size() - 2); + } + } + value += *j; + } + this->Makefile->AddDefinition(var, value.c_str()); + return true; +} + +#if defined(CMAKE_BUILD_WITH_CMAKE) + +// Stuff for curl download/upload +typedef std::vector<char> cmFileCommandVectorOfChar; + +namespace { + +size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data) +{ + int realsize = static_cast<int>(size * nmemb); + cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data); + const char* chPtr = static_cast<char*>(ptr); + fout->write(chPtr, realsize); + return realsize; +} + +size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb, + void* data) +{ + int realsize = static_cast<int>(size * nmemb); + cmFileCommandVectorOfChar* vec = + static_cast<cmFileCommandVectorOfChar*>(data); + const char* chPtr = static_cast<char*>(ptr); + vec->insert(vec->end(), chPtr, chPtr + realsize); + return realsize; +} + +size_t cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr, + size_t size, void* data) +{ + cmFileCommandVectorOfChar* vec = + static_cast<cmFileCommandVectorOfChar*>(data); + switch (type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_IN: + case CURLINFO_HEADER_OUT: + vec->insert(vec->end(), chPtr, chPtr + size); + break; + case CURLINFO_DATA_IN: + case CURLINFO_DATA_OUT: + case CURLINFO_SSL_DATA_IN: + case CURLINFO_SSL_DATA_OUT: { + char buf[128]; + int n = sprintf(buf, "[%" KWIML_INT_PRIu64 " bytes data]\n", + static_cast<KWIML_INT_uint64_t>(size)); + if (n > 0) { + vec->insert(vec->end(), buf, buf + n); + } + } break; + default: + break; + } + return 0; +} + +class cURLProgressHelper +{ +public: + cURLProgressHelper(cmFileCommand* fc, const char* text) + { + this->CurrentPercentage = -1; + this->FileCommand = fc; + this->Text = text; + } + + bool UpdatePercentage(double value, double total, std::string& status) + { + long OldPercentage = this->CurrentPercentage; + + if (total > 0.0) { + this->CurrentPercentage = std::lround(value / total * 100.0); + if (this->CurrentPercentage > 100) { + // Avoid extra progress reports for unexpected data beyond total. + this->CurrentPercentage = 100; + } + } + + bool updated = (OldPercentage != this->CurrentPercentage); + + if (updated) { + std::ostringstream oss; + oss << "[" << this->Text << " " << this->CurrentPercentage + << "% complete]"; + status = oss.str(); + } + + return updated; + } + + cmFileCommand* GetFileCommand() { return this->FileCommand; } + +private: + long CurrentPercentage; + cmFileCommand* FileCommand; + std::string Text; +}; + +int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow, + double ultotal, double ulnow) +{ + cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp); + + static_cast<void>(ultotal); + static_cast<void>(ulnow); + + std::string status; + if (helper->UpdatePercentage(dlnow, dltotal, status)) { + cmFileCommand* fc = helper->GetFileCommand(); + cmMakefile* mf = fc->GetMakefile(); + mf->DisplayStatus(status, -1); + } + + return 0; +} + +int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow, + double ultotal, double ulnow) +{ + cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp); + + static_cast<void>(dltotal); + static_cast<void>(dlnow); + + std::string status; + if (helper->UpdatePercentage(ulnow, ultotal, status)) { + cmFileCommand* fc = helper->GetFileCommand(); + cmMakefile* mf = fc->GetMakefile(); + mf->DisplayStatus(status, -1); + } + + return 0; +} +} + +namespace { + +class cURLEasyGuard +{ +public: + cURLEasyGuard(CURL* easy) + : Easy(easy) + { + } + + ~cURLEasyGuard() + { + if (this->Easy) { + ::curl_easy_cleanup(this->Easy); + } + } + + cURLEasyGuard(const cURLEasyGuard&) = delete; + cURLEasyGuard& operator=(const cURLEasyGuard&) = delete; + + void release() { this->Easy = nullptr; } + +private: + ::CURL* Easy; +}; +} +#endif + +#define check_curl_result(result, errstr) \ + do { \ + if (result != CURLE_OK) { \ + std::string e(errstr); \ + e += ::curl_easy_strerror(result); \ + this->SetError(e); \ + return false; \ + } \ + } while (false) + +bool cmFileCommand::HandleDownloadCommand(std::vector<std::string> const& args) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + std::vector<std::string>::const_iterator i = args.begin(); + if (args.size() < 3) { + this->SetError("DOWNLOAD must be called with at least three arguments."); + return false; + } + ++i; // Get rid of subcommand + std::string url = *i; + ++i; + std::string file = *i; + ++i; + + long timeout = 0; + long inactivity_timeout = 0; + std::string logVar; + std::string statusVar; + bool tls_verify = this->Makefile->IsOn("CMAKE_TLS_VERIFY"); + const char* cainfo = this->Makefile->GetDefinition("CMAKE_TLS_CAINFO"); + std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC"); + std::string netrc_file = + this->Makefile->GetSafeDefinition("CMAKE_NETRC_FILE"); + std::string expectedHash; + std::string hashMatchMSG; + std::unique_ptr<cmCryptoHash> hash; + bool showProgress = false; + std::string userpwd; + + std::vector<std::string> curl_headers; + + while (i != args.end()) { + if (*i == "TIMEOUT") { + ++i; + if (i != args.end()) { + timeout = atol(i->c_str()); + } else { + this->SetError("DOWNLOAD missing time for TIMEOUT."); + return false; + } + } else if (*i == "INACTIVITY_TIMEOUT") { + ++i; + if (i != args.end()) { + inactivity_timeout = atol(i->c_str()); + } else { + this->SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT."); + return false; + } + } else if (*i == "LOG") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing VAR for LOG."); + return false; + } + logVar = *i; + } else if (*i == "STATUS") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing VAR for STATUS."); + return false; + } + statusVar = *i; + } else if (*i == "TLS_VERIFY") { + ++i; + if (i != args.end()) { + tls_verify = cmSystemTools::IsOn(*i); + } else { + this->SetError("TLS_VERIFY missing bool value."); + return false; + } + } else if (*i == "TLS_CAINFO") { + ++i; + if (i != args.end()) { + cainfo = i->c_str(); + } else { + this->SetError("TLS_CAFILE missing file value."); + return false; + } + } else if (*i == "NETRC_FILE") { + ++i; + if (i != args.end()) { + netrc_file = *i; + } else { + this->SetError("DOWNLOAD missing file value for NETRC_FILE."); + return false; + } + } else if (*i == "NETRC") { + ++i; + if (i != args.end()) { + netrc_level = *i; + } else { + this->SetError("DOWNLOAD missing level value for NETRC."); + return false; + } + } else if (*i == "EXPECTED_MD5") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing sum value for EXPECTED_MD5."); + return false; + } + hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5); + hashMatchMSG = "MD5 sum"; + expectedHash = cmSystemTools::LowerCase(*i); + } else if (*i == "SHOW_PROGRESS") { + showProgress = true; + } else if (*i == "EXPECTED_HASH") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH."); + return false; + } + std::string::size_type pos = i->find("="); + if (pos == std::string::npos) { + std::string err = + "DOWNLOAD EXPECTED_HASH expects ALGO=value but got: "; + err += *i; + this->SetError(err); + return false; + } + std::string algo = i->substr(0, pos); + expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1)); + hash = std::unique_ptr<cmCryptoHash>(cmCryptoHash::New(algo.c_str())); + if (!hash) { + std::string err = "DOWNLOAD EXPECTED_HASH given unknown ALGO: "; + err += algo; + this->SetError(err); + return false; + } + hashMatchMSG = algo + " hash"; + } else if (*i == "USERPWD") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing string for USERPWD."); + return false; + } + userpwd = *i; + } else if (*i == "HTTPHEADER") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing string for HTTPHEADER."); + return false; + } + curl_headers.push_back(*i); + } else { + // Do not return error for compatibility reason. + std::string err = "Unexpected argument: "; + err += *i; + this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err); + } + ++i; + } + // If file exists already, and caller specified an expected md5 or sha, + // and the existing file already has the expected hash, then simply + // return. + // + if (cmSystemTools::FileExists(file) && hash.get()) { + std::string msg; + std::string actualHash = hash->HashFile(file); + if (actualHash == expectedHash) { + msg = "returning early; file already exists with expected "; + msg += hashMatchMSG; + msg += "\""; + if (!statusVar.empty()) { + std::ostringstream result; + result << 0 << ";\"" << msg; + this->Makefile->AddDefinition(statusVar, result.str().c_str()); + } + return true; + } + } + // Make sure parent directory exists so we can write to the file + // as we receive downloaded bits from curl... + // + std::string dir = cmSystemTools::GetFilenamePath(file); + if (!cmSystemTools::FileExists(dir) && !cmSystemTools::MakeDirectory(dir)) { + std::string errstring = "DOWNLOAD error: cannot create directory '" + dir + + "' - Specify file by full path name and verify that you " + "have directory creation and file write privileges."; + this->SetError(errstring); + return false; + } + + cmsys::ofstream fout(file.c_str(), std::ios::binary); + if (!fout) { + this->SetError("DOWNLOAD cannot open file for write."); + return false; + } + +# if defined(_WIN32) + url = fix_file_url_windows(url); +# endif + + ::CURL* curl; + ::curl_global_init(CURL_GLOBAL_DEFAULT); + curl = ::curl_easy_init(); + if (!curl) { + this->SetError("DOWNLOAD error initializing curl."); + return false; + } + + cURLEasyGuard g_curl(curl); + ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + check_curl_result(res, "DOWNLOAD cannot set url: "); + + // enable HTTP ERROR parsing + res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + check_curl_result(res, "DOWNLOAD cannot set http failure option: "); + + res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION); + check_curl_result(res, "DOWNLOAD cannot set user agent option: "); + + res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback); + check_curl_result(res, "DOWNLOAD cannot set write function: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, + cmFileCommandCurlDebugCallback); + check_curl_result(res, "DOWNLOAD cannot set debug function: "); + + // check to see if TLS verification is requested + if (tls_verify) { + res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); + check_curl_result(res, "Unable to set TLS/SSL Verify on: "); + } else { + res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + check_curl_result(res, "Unable to set TLS/SSL Verify off: "); + } + // check to see if a CAINFO file has been specified + // command arg comes first + std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo); + if (!cainfo_err.empty()) { + this->SetError(cainfo_err); + return false; + } + + // check to see if netrc parameters have been specified + // local command args takes precedence over CMAKE_NETRC* + netrc_level = cmSystemTools::UpperCase(netrc_level); + std::string const& netrc_option_err = + cmCurlSetNETRCOption(curl, netrc_level, netrc_file); + if (!netrc_option_err.empty()) { + this->SetError(netrc_option_err); + return false; + } + + cmFileCommandVectorOfChar chunkDebug; + + res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fout); + check_curl_result(res, "DOWNLOAD cannot set write data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug); + check_curl_result(res, "DOWNLOAD cannot set debug data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: "); + + if (!logVar.empty()) { + res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + check_curl_result(res, "DOWNLOAD cannot set verbose: "); + } + + if (timeout > 0) { + res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); + check_curl_result(res, "DOWNLOAD cannot set timeout: "); + } + + if (inactivity_timeout > 0) { + // Give up if there is no progress for a long time. + ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); + ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout); + } + + // Need the progress helper's scope to last through the duration of + // the curl_easy_perform call... so this object is declared at function + // scope intentionally, rather than inside the "if(showProgress)" + // block... + // + cURLProgressHelper helper(this, "download"); + + if (showProgress) { + res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + check_curl_result(res, "DOWNLOAD cannot set noprogress value: "); + + res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, + cmFileDownloadProgressCallback); + check_curl_result(res, "DOWNLOAD cannot set progress function: "); + + res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, + reinterpret_cast<void*>(&helper)); + check_curl_result(res, "DOWNLOAD cannot set progress data: "); + } + + if (!userpwd.empty()) { + res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str()); + check_curl_result(res, "DOWNLOAD cannot set user password: "); + } + + struct curl_slist* headers = nullptr; + for (std::string const& h : curl_headers) { + headers = ::curl_slist_append(headers, h.c_str()); + } + ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = ::curl_easy_perform(curl); + + ::curl_slist_free_all(headers); + + /* always cleanup */ + g_curl.release(); + ::curl_easy_cleanup(curl); + + if (!statusVar.empty()) { + std::ostringstream result; + result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res) + << "\""; + this->Makefile->AddDefinition(statusVar, result.str().c_str()); + } + + ::curl_global_cleanup(); + + // Explicitly flush/close so we can measure the md5 accurately. + // + fout.flush(); + fout.close(); + + // Verify MD5 sum if requested: + // + if (hash) { + std::string actualHash = hash->HashFile(file); + if (actualHash.empty()) { + this->SetError("DOWNLOAD cannot compute hash on downloaded file"); + return false; + } + + if (expectedHash != actualHash) { + std::ostringstream oss; + oss << "DOWNLOAD HASH mismatch" << std::endl + << " for file: [" << file << "]" << std::endl + << " expected hash: [" << expectedHash << "]" << std::endl + << " actual hash: [" << actualHash << "]" << std::endl + << " status: [" << static_cast<int>(res) << ";\"" + << ::curl_easy_strerror(res) << "\"]" << std::endl; + + if (!statusVar.empty() && res == 0) { + std::string status = "1;HASH mismatch: " + "expected: " + + expectedHash + " actual: " + actualHash; + this->Makefile->AddDefinition(statusVar, status.c_str()); + } + + this->SetError(oss.str()); + return false; + } + } + + if (!logVar.empty()) { + chunkDebug.push_back(0); + this->Makefile->AddDefinition(logVar, chunkDebug.data()); + } + + return true; +#else + this->SetError("DOWNLOAD not supported by bootstrap cmake."); + return false; +#endif +} + +bool cmFileCommand::HandleUploadCommand(std::vector<std::string> const& args) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + if (args.size() < 3) { + this->SetError("UPLOAD must be called with at least three arguments."); + return false; + } + std::vector<std::string>::const_iterator i = args.begin(); + ++i; + std::string filename = *i; + ++i; + std::string url = *i; + ++i; + + long timeout = 0; + long inactivity_timeout = 0; + std::string logVar; + std::string statusVar; + bool showProgress = false; + std::string userpwd; + std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC"); + std::string netrc_file = + this->Makefile->GetSafeDefinition("CMAKE_NETRC_FILE"); + + std::vector<std::string> curl_headers; + + while (i != args.end()) { + if (*i == "TIMEOUT") { + ++i; + if (i != args.end()) { + timeout = atol(i->c_str()); + } else { + this->SetError("UPLOAD missing time for TIMEOUT."); + return false; + } + } else if (*i == "INACTIVITY_TIMEOUT") { + ++i; + if (i != args.end()) { + inactivity_timeout = atol(i->c_str()); + } else { + this->SetError("UPLOAD missing time for INACTIVITY_TIMEOUT."); + return false; + } + } else if (*i == "LOG") { + ++i; + if (i == args.end()) { + this->SetError("UPLOAD missing VAR for LOG."); + return false; + } + logVar = *i; + } else if (*i == "STATUS") { + ++i; + if (i == args.end()) { + this->SetError("UPLOAD missing VAR for STATUS."); + return false; + } + statusVar = *i; + } else if (*i == "SHOW_PROGRESS") { + showProgress = true; + } else if (*i == "NETRC_FILE") { + ++i; + if (i != args.end()) { + netrc_file = *i; + } else { + this->SetError("UPLOAD missing file value for NETRC_FILE."); + return false; + } + } else if (*i == "NETRC") { + ++i; + if (i != args.end()) { + netrc_level = *i; + } else { + this->SetError("UPLOAD missing level value for NETRC."); + return false; + } + } else if (*i == "USERPWD") { + ++i; + if (i == args.end()) { + this->SetError("UPLOAD missing string for USERPWD."); + return false; + } + userpwd = *i; + } else if (*i == "HTTPHEADER") { + ++i; + if (i == args.end()) { + this->SetError("UPLOAD missing string for HTTPHEADER."); + return false; + } + curl_headers.push_back(*i); + } else { + // Do not return error for compatibility reason. + std::string err = "Unexpected argument: "; + err += *i; + this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err); + } + + ++i; + } + + // Open file for reading: + // + FILE* fin = cmsys::SystemTools::Fopen(filename, "rb"); + if (!fin) { + std::string errStr = "UPLOAD cannot open file '"; + errStr += filename + "' for reading."; + this->SetError(errStr); + return false; + } + + unsigned long file_size = cmsys::SystemTools::FileLength(filename); + +# if defined(_WIN32) + url = fix_file_url_windows(url); +# endif + + ::CURL* curl; + ::curl_global_init(CURL_GLOBAL_DEFAULT); + curl = ::curl_easy_init(); + if (!curl) { + this->SetError("UPLOAD error initializing curl."); + fclose(fin); + return false; + } + + cURLEasyGuard g_curl(curl); + + // enable HTTP ERROR parsing + ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + check_curl_result(res, "UPLOAD cannot set fail on error flag: "); + + // enable uploading + res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); + check_curl_result(res, "UPLOAD cannot set upload flag: "); + + res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + check_curl_result(res, "UPLOAD cannot set url: "); + + res = + ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback); + check_curl_result(res, "UPLOAD cannot set write function: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, + cmFileCommandCurlDebugCallback); + check_curl_result(res, "UPLOAD cannot set debug function: "); + + cmFileCommandVectorOfChar chunkResponse; + cmFileCommandVectorOfChar chunkDebug; + + res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse); + check_curl_result(res, "UPLOAD cannot set write data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug); + check_curl_result(res, "UPLOAD cannot set debug data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + check_curl_result(res, "UPLOAD cannot set follow-redirect option: "); + + if (!logVar.empty()) { + res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + check_curl_result(res, "UPLOAD cannot set verbose: "); + } + + if (timeout > 0) { + res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); + check_curl_result(res, "UPLOAD cannot set timeout: "); + } + + if (inactivity_timeout > 0) { + // Give up if there is no progress for a long time. + ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); + ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout); + } + + // Need the progress helper's scope to last through the duration of + // the curl_easy_perform call... so this object is declared at function + // scope intentionally, rather than inside the "if(showProgress)" + // block... + // + cURLProgressHelper helper(this, "upload"); + + if (showProgress) { + res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + check_curl_result(res, "UPLOAD cannot set noprogress value: "); + + res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, + cmFileUploadProgressCallback); + check_curl_result(res, "UPLOAD cannot set progress function: "); + + res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, + reinterpret_cast<void*>(&helper)); + check_curl_result(res, "UPLOAD cannot set progress data: "); + } + + // now specify which file to upload + res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin); + check_curl_result(res, "UPLOAD cannot set input file: "); + + // and give the size of the upload (optional) + res = + ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size)); + check_curl_result(res, "UPLOAD cannot set input file size: "); + + if (!userpwd.empty()) { + res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str()); + check_curl_result(res, "UPLOAD cannot set user password: "); + } + + // check to see if netrc parameters have been specified + // local command args takes precedence over CMAKE_NETRC* + netrc_level = cmSystemTools::UpperCase(netrc_level); + std::string const& netrc_option_err = + cmCurlSetNETRCOption(curl, netrc_level, netrc_file); + if (!netrc_option_err.empty()) { + this->SetError(netrc_option_err); + return false; + } + + struct curl_slist* headers = nullptr; + for (std::string const& h : curl_headers) { + headers = ::curl_slist_append(headers, h.c_str()); + } + ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = ::curl_easy_perform(curl); + + ::curl_slist_free_all(headers); + + /* always cleanup */ + g_curl.release(); + ::curl_easy_cleanup(curl); + + if (!statusVar.empty()) { + std::ostringstream result; + result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res) + << "\""; + this->Makefile->AddDefinition(statusVar, result.str().c_str()); + } + + ::curl_global_cleanup(); + + fclose(fin); + fin = nullptr; + + if (!logVar.empty()) { + std::string log; + + if (!chunkResponse.empty()) { + chunkResponse.push_back(0); + log += "Response:\n"; + log += chunkResponse.data(); + log += "\n"; + } + + if (!chunkDebug.empty()) { + chunkDebug.push_back(0); + log += "Debug:\n"; + log += chunkDebug.data(); + log += "\n"; + } + + this->Makefile->AddDefinition(logVar, log.c_str()); + } + + return true; +#else + this->SetError("UPLOAD not supported by bootstrap cmake."); + return false; +#endif +} + +void cmFileCommand::AddEvaluationFile(const std::string& inputName, + const std::string& outputExpr, + const std::string& condition, + bool inputIsContent) +{ + cmListFileBacktrace lfbt = this->Makefile->GetBacktrace(); + + cmGeneratorExpression outputGe(lfbt); + std::unique_ptr<cmCompiledGeneratorExpression> outputCge = + outputGe.Parse(outputExpr); + + cmGeneratorExpression conditionGe(lfbt); + std::unique_ptr<cmCompiledGeneratorExpression> conditionCge = + conditionGe.Parse(condition); + + this->Makefile->AddEvaluationFile(inputName, std::move(outputCge), + std::move(conditionCge), inputIsContent); +} + +bool cmFileCommand::HandleGenerateCommand(std::vector<std::string> const& args) +{ + if (args.size() < 5) { + this->SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + if (args[1] != "OUTPUT") { + this->SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + std::string condition; + if (args.size() > 5) { + if (args[5] != "CONDITION") { + this->SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + if (args.size() != 7) { + this->SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + condition = args[6]; + if (condition.empty()) { + this->SetError("CONDITION of sub-command GENERATE must not be empty if " + "specified."); + return false; + } + } + std::string output = args[2]; + const bool inputIsContent = args[3] != "INPUT"; + if (inputIsContent && args[3] != "CONTENT") { + this->SetError("Incorrect arguments to GENERATE subcommand."); + return false; + } + std::string input = args[4]; + + this->AddEvaluationFile(input, output, condition, inputIsContent); + return true; +} + +bool cmFileCommand::HandleLockCommand(std::vector<std::string> const& args) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + // Default values + bool directory = false; + bool release = false; + enum Guard + { + GUARD_FUNCTION, + GUARD_FILE, + GUARD_PROCESS + }; + Guard guard = GUARD_PROCESS; + std::string resultVariable; + unsigned long timeout = static_cast<unsigned long>(-1); + + // Parse arguments + if (args.size() < 2) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "sub-command LOCK requires at least two arguments."); + return false; + } + + std::string path = args[1]; + for (unsigned i = 2; i < args.size(); ++i) { + if (args[i] == "DIRECTORY") { + directory = true; + } else if (args[i] == "RELEASE") { + release = true; + } else if (args[i] == "GUARD") { + ++i; + const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD"; + if (i >= args.size()) { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, merr); + return false; + } + if (args[i] == "FUNCTION") { + guard = GUARD_FUNCTION; + } else if (args[i] == "FILE") { + guard = GUARD_FILE; + } else if (args[i] == "PROCESS") { + guard = GUARD_PROCESS; + } else { + std::ostringstream e; + e << merr << ", but got:\n \"" << args[i] << "\"."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return false; + } + + } else if (args[i] == "RESULT_VARIABLE") { + ++i; + if (i >= args.size()) { + this->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + "expected variable name after RESULT_VARIABLE"); + return false; + } + resultVariable = args[i]; + } else if (args[i] == "TIMEOUT") { + ++i; + if (i >= args.size()) { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, + "expected timeout value after TIMEOUT"); + return false; + } + long scanned; + if (!cmSystemTools::StringToLong(args[i].c_str(), &scanned) || + scanned < 0) { + std::ostringstream e; + e << "TIMEOUT value \"" << args[i] << "\" is not an unsigned integer."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return false; + } + timeout = static_cast<unsigned long>(scanned); + } else { + std::ostringstream e; + e << "expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT\n"; + e << "but got: \"" << args[i] << "\"."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return false; + } + } + + if (directory) { + path += "/cmake.lock"; + } + + if (!cmsys::SystemTools::FileIsFullPath(path)) { + path = this->Makefile->GetCurrentSourceDirectory() + "/" + path; + } + + // Unify path (remove '//', '/../', ...) + path = cmSystemTools::CollapseFullPath(path); + + // Create file and directories if needed + std::string parentDir = cmSystemTools::GetParentDirectory(path); + if (!cmSystemTools::MakeDirectory(parentDir)) { + std::ostringstream e; + e << "directory\n \"" << parentDir << "\"\ncreation failed "; + e << "(check permissions)."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + FILE* file = cmsys::SystemTools::Fopen(path, "w"); + if (!file) { + std::ostringstream e; + e << "file\n \"" << path << "\"\ncreation failed (check permissions)."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + fclose(file); + + // Actual lock/unlock + cmFileLockPool& lockPool = + this->Makefile->GetGlobalGenerator()->GetFileLockPool(); + + cmFileLockResult fileLockResult(cmFileLockResult::MakeOk()); + if (release) { + fileLockResult = lockPool.Release(path); + } else { + switch (guard) { + case GUARD_FUNCTION: + fileLockResult = lockPool.LockFunctionScope(path, timeout); + break; + case GUARD_FILE: + fileLockResult = lockPool.LockFileScope(path, timeout); + break; + case GUARD_PROCESS: + fileLockResult = lockPool.LockProcessScope(path, timeout); + break; + default: + cmSystemTools::SetFatalErrorOccured(); + return false; + } + } + + const std::string result = fileLockResult.GetOutputMessage(); + + if (resultVariable.empty() && !fileLockResult.IsOk()) { + std::ostringstream e; + e << "error locking file\n \"" << path << "\"\n" << result << "."; + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + + if (!resultVariable.empty()) { + this->Makefile->AddDefinition(resultVariable, result.c_str()); + } + + return true; +#else + static_cast<void>(args); + this->SetError("sub-command LOCK not implemented in bootstrap cmake"); + return false; +#endif +} + +bool cmFileCommand::HandleTimestampCommand( + std::vector<std::string> const& args) +{ + if (args.size() < 3) { + this->SetError("sub-command TIMESTAMP requires at least two arguments."); + return false; + } + if (args.size() > 5) { + this->SetError("sub-command TIMESTAMP takes at most four arguments."); + return false; + } + + unsigned int argsIndex = 1; + + const std::string& filename = args[argsIndex++]; + + const std::string& outputVariable = args[argsIndex++]; + + std::string formatString; + if (args.size() > argsIndex && args[argsIndex] != "UTC") { + formatString = args[argsIndex++]; + } + + bool utcFlag = false; + if (args.size() > argsIndex) { + if (args[argsIndex] == "UTC") { + utcFlag = true; + } else { + std::string e = " TIMESTAMP sub-command does not recognize option " + + args[argsIndex] + "."; + this->SetError(e); + return false; + } + } + + cmTimestamp timestamp; + std::string result = + timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag); + this->Makefile->AddDefinition(outputVariable, result.c_str()); + + return true; +} + +bool cmFileCommand::HandleSizeCommand(std::vector<std::string> const& args) +{ + if (args.size() != 3) { + std::ostringstream e; + e << args[0] << " requires a file name and output variable"; + this->SetError(e.str()); + return false; + } + + unsigned int argsIndex = 1; + + const std::string& filename = args[argsIndex++]; + + const std::string& outputVariable = args[argsIndex++]; + + if (!cmSystemTools::FileExists(filename, true)) { + std::ostringstream e; + e << "SIZE requested of path that is not readable:\n " << filename; + this->SetError(e.str()); + return false; + } + + this->Makefile->AddDefinition( + outputVariable, + std::to_string(cmSystemTools::FileLength(filename)).c_str()); + + return true; +} + +bool cmFileCommand::HandleReadSymlinkCommand( + std::vector<std::string> const& args) +{ + if (args.size() != 3) { + std::ostringstream e; + e << args[0] << " requires a file name and output variable"; + this->SetError(e.str()); + return false; + } + + const std::string& filename = args[1]; + const std::string& outputVariable = args[2]; + + std::string result; + if (!cmSystemTools::ReadSymlink(filename, result)) { + std::ostringstream e; + e << "READ_SYMLINK requested of path that is not a symlink:\n " + << filename; + this->SetError(e.str()); + return false; + } + + this->Makefile->AddDefinition(outputVariable, result.c_str()); + + return true; +} + +bool cmFileCommand::HandleCreateLinkCommand( + std::vector<std::string> const& args) +{ + if (args.size() < 3) { + this->SetError("CREATE_LINK must be called with at least two additional " + "arguments"); + return false; + } + + cmCommandArgumentsHelper argHelper; + cmCommandArgumentGroup group; + + cmCAString linkArg(&argHelper, "CREATE_LINK"); + cmCAString fileArg(&argHelper, nullptr); + cmCAString newFileArg(&argHelper, nullptr); + + cmCAString resultArg(&argHelper, "RESULT", &group); + cmCAEnabler copyOnErrorArg(&argHelper, "COPY_ON_ERROR", &group); + cmCAEnabler symbolicArg(&argHelper, "SYMBOLIC", &group); + + linkArg.Follows(nullptr); + fileArg.Follows(&linkArg); + newFileArg.Follows(&fileArg); + group.Follows(&newFileArg); + + std::vector<std::string> unconsumedArgs; + argHelper.Parse(&args, &unconsumedArgs); + + if (!unconsumedArgs.empty()) { + this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"'); + return false; + } + + std::string fileName = fileArg.GetString(); + std::string newFileName = newFileArg.GetString(); + + // Output variable for storing the result. + const std::string& resultVar = resultArg.GetString(); + + // The system error message generated in the operation. + std::string result; + + // Check if the paths are distinct. + if (fileName == newFileName) { + result = "CREATE_LINK cannot use same file and newfile"; + if (!resultVar.empty()) { + this->Makefile->AddDefinition(resultVar, result.c_str()); + return true; + } + this->SetError(result); + return false; + } + + // Hard link requires original file to exist. + if (!symbolicArg.IsEnabled() && !cmSystemTools::FileExists(fileName)) { + result = "Cannot hard link \'" + fileName + "\' as it does not exist."; + if (!resultVar.empty()) { + this->Makefile->AddDefinition(resultVar, result.c_str()); + return true; + } + this->SetError(result); + return false; + } + + // Check if the new file already exists and remove it. + if ((cmSystemTools::FileExists(newFileName) || + cmSystemTools::FileIsSymlink(newFileName)) && + !cmSystemTools::RemoveFile(newFileName)) { + std::ostringstream e; + e << "Failed to create link '" << newFileName + << "' because existing path cannot be removed: " + << cmSystemTools::GetLastSystemError() << "\n"; + + if (!resultVar.empty()) { + this->Makefile->AddDefinition(resultVar, e.str().c_str()); + return true; + } + this->SetError(e.str()); + return false; + } + + // Whether the operation completed successfully. + bool completed = false; + + // Check if the command requires a symbolic link. + if (symbolicArg.IsEnabled()) { + completed = cmSystemTools::CreateSymlink(fileName, newFileName, &result); + } else { + completed = cmSystemTools::CreateLink(fileName, newFileName, &result); + } + + // Check if copy-on-error is enabled in the arguments. + if (!completed && copyOnErrorArg.IsEnabled()) { + completed = cmsys::SystemTools::CopyFileAlways(fileName, newFileName); + if (!completed) { + result = "Copy failed: " + cmSystemTools::GetLastSystemError(); + } + } + + // Check if the operation was successful. + if (completed) { + result = "0"; + } else if (resultVar.empty()) { + // The operation failed and the result is not reported in a variable. + this->SetError(result); + return false; + } + + if (!resultVar.empty()) { + this->Makefile->AddDefinition(resultVar, result.c_str()); + } + + return true; +} |