/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmFileInstaller.h" #include #include #include #include #include #include "cm_sys_stat.h" #include "cmExecutionStatus.h" #include "cmFSPermissions.h" #include "cmMakefile.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" using namespace cmFSPermissions; cmFileInstaller::cmFileInstaller(cmExecutionStatus& status) : cmFileCopier(status, "INSTALL") { // 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 = cmIsOn(install_always); } // Get the current manifest. this->Manifest = this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES"); } cmFileInstaller::~cmFileInstaller() { // Save the updated install manifest. this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", this->Manifest); } void cmFileInstaller::ManifestAppend(std::string const& file) { if (!this->Manifest.empty()) { this->Manifest += ";"; } this->Manifest += file.substr(this->DestDirLength); } std::string const& cmFileInstaller::ToName(std::string const& fromName) { return this->Rename.empty() ? fromName : this->Rename; } void cmFileInstaller::ReportCopy(const std::string& toFile, Type type, bool copy) { if (!this->MessageNever && (copy || !this->MessageLazy)) { std::string message = cmStrCat((copy ? "Installing: " : "Up-to-date: "), toFile); this->Makefile->DisplayStatus(message, -1); } if (type != TypeDir) { // Add the file to the manifest. this->ManifestAppend(toFile); } } bool cmFileInstaller::ReportMissing(const std::string& fromFile) { return (this->Optional || this->cmFileCopier::ReportMissing(fromFile)); } bool cmFileInstaller::Install(const std::string& fromFile, const std::string& toFile) { // 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 cmFileInstaller::InstallFile(const std::string& fromFile, const std::string& toFile, MatchProperties match_properties) { if (this->InstallMode == cmInstallMode::COPY) { return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties); } std::string newFromFile; if (this->InstallMode == cmInstallMode::REL_SYMLINK || this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY || this->InstallMode == cmInstallMode::SYMLINK || this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) { // Try to get a relative path. std::string toDir = cmSystemTools::GetParentDirectory(toFile); newFromFile = cmSystemTools::ForceToRelativePath(toDir, fromFile); // Double check that we can restore the original path. std::string reassembled = cmSystemTools::CollapseFullPath(newFromFile, toDir); if (!cmSystemTools::ComparePath(reassembled, fromFile)) { if (this->InstallMode == cmInstallMode::SYMLINK || this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) { // User does not mind, silently proceed with absolute path. newFromFile = fromFile; } else if (this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY) { // User expects a relative symbolic link or a copy. // Since an absolute symlink won't do, copy instead. return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties); } else { // We cannot meet user's expectation (REL_SYMLINK) auto e = cmStrCat(this->Name, " cannot determine relative path for symlink to \"", newFromFile, "\" at \"", toFile, "\"."); this->Status.SetError(e); return false; } } } else { newFromFile = fromFile; // stick with absolute path } // 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 (newFromFile == 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(newFromFile, toFile)) { if (this->InstallMode == cmInstallMode::ABS_SYMLINK_OR_COPY || this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY || this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) { // Failed to create a symbolic link, fall back to copying. return this->cmFileCopier::InstallFile(newFromFile, toFile, match_properties); } auto e = cmStrCat(this->Name, " cannot create symlink to \"", newFromFile, "\" at \"", toFile, "\": ", cmSystemTools::GetLastSystemError(), "\"."); this->Status.SetError(e); return false; } } return true; } void cmFileInstaller::DefaultFilePermissions() { 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 cmFileInstaller::Parse(std::vector const& args) { if (!this->cmFileCopier::Parse(args)) { return false; } if (!this->Rename.empty()) { if (!this->FilesFromDir.empty()) { this->Status.SetError("INSTALL option RENAME may not be " "combined with FILES_FROM_DIR."); return false; } if (this->InstallType != cmInstallType_FILES && this->InstallType != cmInstallType_PROGRAMS) { this->Status.SetError("INSTALL option RENAME may be used " "only with FILES or PROGRAMS."); return false; } if (this->Files.size() > 1) { this->Status.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->Status.SetError("INSTALL options MESSAGE_ALWAYS, " "MESSAGE_LAZY, and MESSAGE_NEVER " "are mutually exclusive."); return false; } static const std::map install_mode_dict{ { "ABS_SYMLINK"_s, cmInstallMode::ABS_SYMLINK }, { "ABS_SYMLINK_OR_COPY"_s, cmInstallMode::ABS_SYMLINK_OR_COPY }, { "REL_SYMLINK"_s, cmInstallMode::REL_SYMLINK }, { "REL_SYMLINK_OR_COPY"_s, cmInstallMode::REL_SYMLINK_OR_COPY }, { "SYMLINK"_s, cmInstallMode::SYMLINK }, { "SYMLINK_OR_COPY"_s, cmInstallMode::SYMLINK_OR_COPY } }; std::string install_mode; cmSystemTools::GetEnv("CMAKE_INSTALL_MODE", install_mode); if (install_mode.empty() || install_mode == "COPY"_s) { this->InstallMode = cmInstallMode::COPY; } else { auto it = install_mode_dict.find(install_mode); if (it != install_mode_dict.end()) { this->InstallMode = it->second; } else { auto e = cmStrCat("Unrecognized value '", install_mode, "' for environment variable CMAKE_INSTALL_MODE"); this->Status.SetError(e); 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->Status.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->Status.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->Status.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->Status.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 = cmStrCat("called with network path DESTINATION. This " "does not make sense when using DESTDIR. Specify local " "absolute path or remove DESTDIR environment variable." "\nDESTINATION=\n", destination); this->Status.SetError(message); return false; } } destination = sdestdir + destination.substr(skip); this->DestDirLength = static_cast(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->Status.SetError(errstring); return false; } } if (!cmSystemTools::FileIsDirectory(destination)) { std::string errstring = "INSTALL destination: " + destination + " is not a directory."; this->Status.SetError(errstring); return false; } } return true; }