| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file LICENSE.rst or https://cmake.org/licensing for details. */ |
| |
| #include "cmFileCopier.h" |
| |
| #include "cmsys/Directory.hxx" |
| #include "cmsys/Glob.hxx" |
| |
| #include "cmExecutionStatus.h" |
| #include "cmFSPermissions.h" |
| #include "cmFileTimes.h" |
| #include "cmList.h" |
| #include "cmMakefile.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmValue.h" |
| |
| #ifdef _WIN32 |
| # include <winerror.h> |
| |
| # include "cmsys/FStream.hxx" |
| #else |
| # include <cerrno> |
| #endif |
| |
| #include <cstring> |
| #include <sstream> |
| |
| using namespace cmFSPermissions; |
| |
| cmFileCopier::cmFileCopier(cmExecutionStatus& status, char const* name) |
| : Status(status) |
| , Makefile(&status.GetMakefile()) |
| , Name(name) |
| { |
| } |
| |
| cmFileCopier::~cmFileCopier() = default; |
| |
| cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties( |
| std::string const& file) |
| { |
| // Match rules are case-insensitive on some platforms. |
| #if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__) |
| std::string const file_to_match = cmSystemTools::LowerCase(file); |
| #else |
| std::string const& 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(std::string const& 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. |
| cmFileTimes file_time_orig(toFile); |
| { |
| cmsys::ofstream permissionStream(mode_t_adt_filename.c_str()); |
| if (permissionStream) { |
| permissionStream << std::oct << permissions << std::endl; |
| } |
| permissionStream.close(); |
| } |
| file_time_orig.Store(toFile); |
| } |
| #endif |
| |
| auto perm_status = cmSystemTools::SetPermissions(toFile, permissions); |
| if (!perm_status) { |
| std::ostringstream e; |
| e << this->Name << " cannot set permissions on \"" << toFile |
| << "\": " << perm_status.GetString() << "."; |
| this->Status.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->Status.SetError(e.str()); |
| return false; |
| } |
| return true; |
| } |
| |
| std::string const& cmFileCopier::ToName(std::string const& fromName) |
| { |
| return fromName; |
| } |
| |
| bool cmFileCopier::ReportMissing(std::string const& fromFile) |
| { |
| // The input file does not exist and installation is not optional. |
| this->Status.SetError(cmStrCat(this->Name, " cannot find \"", fromFile, |
| "\": ", cmSystemTools::GetLastSystemError(), |
| '.')); |
| return false; |
| } |
| |
| void cmFileCopier::NotBeforeMatch(std::string const& arg) |
| { |
| std::ostringstream e; |
| e << "option " << arg << " may not appear before PATTERN or REGEX."; |
| this->Status.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->Status.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 |
| cmValue default_dir_install_permissions = this->Makefile->GetDefinition( |
| "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS"); |
| if (cmNonempty(default_dir_install_permissions)) { |
| cmList items{ *default_dir_install_permissions }; |
| for (auto const& arg : items) { |
| if (!this->CheckPermissions(arg, **mode)) { |
| this->Status.SetError( |
| " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable."); |
| 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->Status.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->Status.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 == "FOLLOW_SYMLINK_CHAIN") { |
| this->FollowSymlinkChain = true; |
| this->Doing = DoingNone; |
| } 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 = |
| cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg); |
| } |
| this->Doing = DoingNone; |
| break; |
| case DoingFilesFromDir: |
| if (cmSystemTools::FileIsFullPath(arg)) { |
| this->FilesFromDir = arg; |
| } else { |
| this->FilesFromDir = |
| cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', 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 = |
| cmStrCat('/', cmsys::Glob::PatternToRegex(arg, false), '$'); |
| 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->Status.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->Status.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->Status.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(std::string const& fromFile, |
| std::string const& toFile) |
| { |
| if (fromFile.empty()) { |
| this->Status.SetError( |
| "INSTALL encountered an empty string input file name."); |
| 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; |
| } |
| |
| std::string newFromFile = fromFile; |
| std::string newToFile = toFile; |
| |
| if (this->FollowSymlinkChain && |
| !this->InstallSymlinkChain(newFromFile, newToFile)) { |
| return false; |
| } |
| |
| if (cmSystemTools::FileIsSymlink(newFromFile)) { |
| return this->InstallSymlink(newFromFile, newToFile); |
| } |
| if (cmSystemTools::FileIsDirectory(newFromFile)) { |
| return this->InstallDirectory(newFromFile, newToFile, match_properties); |
| } |
| if (cmSystemTools::FileExists(newFromFile)) { |
| return this->InstallFile(newFromFile, newToFile, match_properties); |
| } |
| return this->ReportMissing(newFromFile); |
| } |
| |
| bool cmFileCopier::InstallSymlinkChain(std::string& fromFile, |
| std::string& toFile) |
| { |
| std::string newFromFile; |
| std::string toFilePath = cmSystemTools::GetFilenamePath(toFile); |
| while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) { |
| if (!cmSystemTools::FileIsFullPath(newFromFile)) { |
| std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile); |
| newFromFile = cmStrCat(fromFilePath, "/", newFromFile); |
| } |
| |
| std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile); |
| |
| bool copy = true; |
| if (!this->Always) { |
| std::string oldSymlinkTarget; |
| if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) { |
| if (symlinkTarget == oldSymlinkTarget) { |
| copy = false; |
| } |
| } |
| } |
| |
| this->ReportCopy(toFile, TypeLink, copy); |
| |
| if (copy) { |
| cmSystemTools::RemoveFile(toFile); |
| cmSystemTools::MakeDirectory(toFilePath); |
| |
| cmsys::Status status = |
| cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile); |
| if (!status) { |
| std::string e = cmStrCat(this->Name, " cannot create symlink\n ", |
| toFile, "\nbecause: ", status.GetString()); |
| this->Status.SetError(e); |
| return false; |
| } |
| } |
| |
| fromFile = newFromFile; |
| toFile = cmStrCat(toFilePath, "/", symlinkTarget); |
| } |
| |
| return true; |
| } |
| |
| bool cmFileCopier::InstallSymlink(std::string const& fromFile, |
| std::string const& toFile) |
| { |
| // Read the original symlink. |
| std::string symlinkTarget; |
| auto read_symlink_status = |
| cmSystemTools::ReadSymlink(fromFile, symlinkTarget); |
| if (!read_symlink_status) { |
| std::ostringstream e; |
| e << this->Name << " cannot read symlink \"" << fromFile |
| << "\" to duplicate at \"" << toFile |
| << "\": " << read_symlink_status.GetString() << "."; |
| this->Status.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. |
| cmsys::Status status = |
| cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile); |
| if (!status) { |
| #ifdef _WIN32 |
| bool const errorFileExists = status.GetWindows() == ERROR_FILE_EXISTS; |
| #else |
| bool const errorFileExists = status.GetPOSIX() == EEXIST; |
| #endif |
| std::string reason; |
| if (errorFileExists && cmSystemTools::FileIsDirectory(toFile)) { |
| reason = "A directory already exists at that location"; |
| } else { |
| reason = status.GetString(); |
| } |
| std::string e = |
| cmStrCat(this->Name, " cannot duplicate symlink\n ", fromFile, |
| "\nat\n ", toFile, "\nbecause: ", reason); |
| this->Status.SetError(e); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool cmFileCopier::InstallFile(std::string const& fromFile, |
| std::string const& 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.DifferS(fromFile, toFile)) { |
| copy = false; |
| } |
| } |
| |
| // Inform the user about this file installation. |
| this->ReportCopy(toFile, TypeFile, copy); |
| |
| // Copy the file. |
| if (copy) { |
| auto copy_status = cmSystemTools::CopyAFile(fromFile, toFile, true); |
| if (!copy_status) { |
| std::ostringstream e; |
| e << this->Name << " cannot copy file \"" << fromFile << "\" to \"" |
| << toFile << "\": " << copy_status.GetString() << "."; |
| this->Status.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); |
| } |
| auto copy_status = cmFileTimes::Copy(fromFile, toFile); |
| if (!copy_status) { |
| std::ostringstream e; |
| e << this->Name << " cannot set modification time on \"" << toFile |
| << "\": " << copy_status.GetString() << "."; |
| this->Status.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(std::string const& source, |
| std::string const& destination, |
| MatchProperties match_properties) |
| { |
| // Inform the user about this directory installation. |
| this->ReportCopy(destination, TypeDir, |
| !( // Report "Up-to-date:" for existing directories, |
| // but not symlinks to them. |
| cmSystemTools::FileIsDirectory(destination) && |
| !cmSystemTools::FileIsSymlink(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. |
| auto makedir_status = |
| cmSystemTools::MakeDirectory(destination, default_dir_mode); |
| if (!makedir_status) { |
| std::ostringstream e; |
| e << this->Name << " cannot make directory \"" << destination |
| << "\": " << makedir_status.GetString() << "."; |
| this->Status.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 = dir.GetNumberOfFiles(); |
| for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) { |
| if (!(strcmp(dir.GetFile(fileNum), ".") == 0 || |
| strcmp(dir.GetFile(fileNum), "..") == 0)) { |
| std::string fromPath = cmStrCat(source, '/', dir.GetFile(fileNum)); |
| std::string toPath = cmStrCat(destination, '/', dir.GetFile(fileNum)); |
| if (!this->Install(fromPath, toPath)) { |
| return false; |
| } |
| } |
| } |
| |
| // Set the requested permissions of the destination directory. |
| return this->SetPermissions(destination, permissions_after); |
| } |