| /* 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 <utility> | |
| 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) { | |
| this->Status.SetError(cmStrCat(this->Name, | |
| " cannot set permissions on \"", toFile, | |
| "\": ", perm_status.GetString(), '.')); | |
| 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)) { | |
| this->Status.SetError( | |
| cmStrCat(this->Name, " given invalid permission \"", arg, "\".")); | |
| 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) | |
| { | |
| this->Status.SetError( | |
| cmStrCat("option ", arg, " may not appear before PATTERN or REGEX.")); | |
| this->Doing = DoingError; | |
| } | |
| void cmFileCopier::NotAfterMatch(std::string const& arg) | |
| { | |
| this->Status.SetError( | |
| cmStrCat("option ", arg, " may not appear after PATTERN or REGEX.")); | |
| 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])) { | |
| this->Status.SetError( | |
| cmStrCat("called with unknown argument \"", args[i], "\".")); | |
| return false; | |
| } | |
| // Quit if an argument is invalid. | |
| if (this->Doing == DoingError) { | |
| return false; | |
| } | |
| } | |
| // Require a destination. | |
| if (this->Destination.empty()) { | |
| this->Status.SetError(cmStrCat(this->Name, " given no DESTINATION")); | |
| 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 { | |
| this->Status.SetError( | |
| cmStrCat("could not compile PATTERN \"", arg, "\".")); | |
| 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 { | |
| this->Status.SetError( | |
| cmStrCat("could not compile REGEX \"", arg, "\".")); | |
| 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 = cmStrCat(this->FilesFromDir, '/', f); | |
| } else { | |
| file = cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', 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 = cmStrCat(std::move(toFile), '/', dir); | |
| } | |
| } | |
| std::string const& toName = this->ToName(fromName); | |
| if (!toName.empty()) { | |
| toFile = cmStrCat(std::move(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 = cmStrCat(std::move(fromFile), '/', std::move(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, '/', std::move(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) { | |
| this->Status.SetError(cmStrCat( | |
| this->Name, " cannot read symlink \"", fromFile, "\" to duplicate at \"", | |
| toFile, "\": ", read_symlink_status.GetString(), '.')); | |
| 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); | |
| if (!copy_status) { | |
| this->Status.SetError(cmStrCat(this->Name, " cannot copy file \"", | |
| fromFile, "\" to \"", toFile, | |
| "\": ", copy_status.GetString(), '.')); | |
| 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) { | |
| this->Status.SetError( | |
| cmStrCat(this->Name, " cannot set modification time on \"", toFile, | |
| "\": ", copy_status.GetString(), '.')); | |
| 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) { | |
| this->Status.SetError(cmStrCat(this->Name, " cannot make directory \"", | |
| destination, | |
| "\": ", makedir_status.GetString(), '.')); | |
| 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) { | |
| std::string const& file = dir.GetFileName(fileNum); | |
| if (file != "." && file != "..") { | |
| std::string fromPath = cmStrCat(source, '/', file); | |
| std::string toPath = cmStrCat(destination, '/', file); | |
| if (!this->Install(fromPath, toPath)) { | |
| return false; | |
| } | |
| } | |
| } | |
| // Set the requested permissions of the destination directory. | |
| return this->SetPermissions(destination, permissions_after); | |
| } |