From e2e8f6b132967844a8bafc866d6cb5c90342af08 Mon Sep 17 00:00:00 2001 From: Bryon Bean Date: Wed, 13 Mar 2019 11:51:12 -0400 Subject: cmFileCommand: Factor out cmFileCopier and cmFileInstaller Split these classes out into their own sources. --- Source/CMakeLists.txt | 4 + Source/cmFileCommand.cxx | 1082 +------------------------------------------- Source/cmFileCopier.cxx | 660 +++++++++++++++++++++++++++ Source/cmFileCopier.h | 120 +++++ Source/cmFileInstaller.cxx | 350 ++++++++++++++ Source/cmFileInstaller.h | 55 +++ bootstrap | 2 + 7 files changed, 1193 insertions(+), 1080 deletions(-) create mode 100644 Source/cmFileCopier.cxx create mode 100644 Source/cmFileCopier.h create mode 100644 Source/cmFileInstaller.cxx create mode 100644 Source/cmFileInstaller.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 2db276d..696826f 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -224,6 +224,10 @@ set(SRCS cmFileAPICodemodel.h cmFileAPICMakeFiles.cxx cmFileAPICMakeFiles.h + cmFileCopier.cxx + cmFileCopier.h + cmFileInstaller.cxx + cmFileInstaller.h cmFileLock.cxx cmFileLock.h cmFileLockPool.cxx diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index a2018c3..d2bc851 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -3,7 +3,6 @@ #include "cmFileCommand.h" #include "cm_kwiml.h" -#include "cmsys/Directory.hxx" #include "cmsys/FStream.hxx" #include "cmsys/Glob.hxx" #include "cmsys/RegularExpression.hxx" @@ -16,20 +15,18 @@ #include #include #include -#include #include #include #include "cmAlgorithms.h" #include "cmCommandArgumentsHelper.h" #include "cmCryptoHash.h" -#include "cmFSPermissions.h" +#include "cmFileCopier.h" +#include "cmFileInstaller.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" @@ -56,8 +53,6 @@ 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. @@ -1058,1085 +1053,12 @@ bool cmFileCommand::HandleDifferentCommand( 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 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 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 Files; - int Doing; - - virtual bool Parse(std::vector 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 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 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 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 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(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 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 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 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 const& args) { diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx new file mode 100644 index 0000000..560d893 --- /dev/null +++ b/Source/cmFileCopier.cxx @@ -0,0 +1,660 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmFileCopier.h" + +#include "cmFSPermissions.h" +#include "cmFileCommand.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmsys/Directory.hxx" +#include "cmsys/Glob.hxx" + +#ifdef _WIN32 +# include "cmsys/FStream.hxx" +#endif + +#include +#include + +using namespace cmFSPermissions; + +cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name) + : 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) +{ +} + +cmFileCopier::~cmFileCopier() = default; + +cmFileCopier::MatchProperties cmFileCopier::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 cmFileCopier::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 cmFileCopier::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; +} + +std::string const& cmFileCopier::ToName(std::string const& fromName) +{ + return fromName; +} + +bool cmFileCopier::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; +} + +void cmFileCopier::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 cmFileCopier::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; +} + +void cmFileCopier::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; +} + +void cmFileCopier::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 cmFileCopier::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 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 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 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 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(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); +} diff --git a/Source/cmFileCopier.h b/Source/cmFileCopier.h new file mode 100644 index 0000000..a11c371 --- /dev/null +++ b/Source/cmFileCopier.h @@ -0,0 +1,120 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmFileCopier_h +#define cmFileCopier_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmFileTimeComparison.h" +#include "cm_sys_stat.h" +#include "cmsys/RegularExpression.hxx" + +#include +#include + +class cmFileCommand; +class cmMakefile; + +// File installation helper class. +struct cmFileCopier +{ + cmFileCopier(cmFileCommand* command, const char* name = "COPY"); + virtual ~cmFileCopier(); + + bool Run(std::vector 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 MatchRules; + + // Get the properties from rules matching this input file. + MatchProperties CollectMatchProperties(const std::string& file); + + bool SetPermissions(const std::string& toFile, mode_t permissions); + + // Translate an argument to a permissions bit. + bool CheckPermissions(std::string const& arg, mode_t& permissions); + + 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); + + enum Type + { + TypeFile, + TypeDir, + TypeLink + }; + virtual void ReportCopy(const std::string&, Type, bool) {} + virtual bool ReportMissing(const std::string& fromFile); + + MatchRule* CurrentMatchRule; + bool UseGivenPermissionsFile; + bool UseGivenPermissionsDir; + bool UseSourcePermissions; + std::string Destination; + std::string FilesFromDir; + std::vector Files; + int Doing; + + virtual bool Parse(std::vector 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); + void NotAfterMatch(std::string const& arg); + virtual void DefaultFilePermissions(); + virtual void DefaultDirectoryPermissions(); + + bool GetDefaultDirectoryPermissions(mode_t** mode); +}; + +#endif diff --git a/Source/cmFileInstaller.cxx b/Source/cmFileInstaller.cxx new file mode 100644 index 0000000..f3544c1 --- /dev/null +++ b/Source/cmFileInstaller.cxx @@ -0,0 +1,350 @@ +/* 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 "cmFSPermissions.h" +#include "cmFileCommand.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" + +#include "cm_sys_stat.h" + +#include + +using namespace cmFSPermissions; + +cmFileInstaller::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::~cmFileInstaller() +{ + // Save the updated install manifest. + this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", + this->Manifest.c_str()); +} + +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 = (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 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); +} + +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->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; +} diff --git a/Source/cmFileInstaller.h b/Source/cmFileInstaller.h new file mode 100644 index 0000000..312529a --- /dev/null +++ b/Source/cmFileInstaller.h @@ -0,0 +1,55 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmFileInstaller_h +#define cmFileInstaller_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmFileCopier.h" + +#include "cmInstallType.h" + +#include +#include + +class cmFileCommand; + +struct cmFileInstaller : public cmFileCopier +{ + cmFileInstaller(cmFileCommand* command); + ~cmFileInstaller() override; + +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); + + std::string const& ToName(std::string const& fromName) override; + + void ReportCopy(const std::string& toFile, Type type, bool copy) override; + bool ReportMissing(const std::string& fromFile) override; + bool Install(const std::string& fromFile, + const std::string& toFile) override; + + bool Parse(std::vector 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; + bool GetTargetTypeFromString(const std::string& stype); + bool HandleInstallDestination(); +}; + +#endif diff --git a/bootstrap b/bootstrap index 26c5212..a3cd6f0 100755 --- a/bootstrap +++ b/bootstrap @@ -302,6 +302,8 @@ CMAKE_CXX_SOURCES="\ cmExprParserHelper \ cmExternalMakefileProjectGenerator \ cmFileCommand \ + cmFileCopier \ + cmFileInstaller \ cmFileTimeComparison \ cmFindBase \ cmFindCommon \ -- cgit v0.12