| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- /* 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 "cmExecutionStatus.h"
- #include "cmFSPermissions.h"
- #include "cmFileTimes.h"
- #include "cmMakefile.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmsys/Directory.hxx"
- #include "cmsys/Glob.hxx"
- #ifdef _WIN32
- # include "cmsys/FStream.hxx"
- #endif
- #include <sstream>
- #include <string.h>
- using namespace cmFSPermissions;
- cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
- : Status(status)
- , Makefile(&status.GetMakefile())
- , Name(name)
- , Always(false)
- , MatchlessFiles(true)
- , FilePermissions(0)
- , DirPermissions(0)
- , CurrentMatchRule(nullptr)
- , UseGivenPermissionsFile(false)
- , UseGivenPermissionsDir(false)
- , UseSourcePermissions(true)
- , FollowSymlinkChain(false)
- , 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.
- 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
- if (!cmSystemTools::SetPermissions(toFile, permissions)) {
- std::ostringstream e;
- e << this->Name << " cannot set permissions on \"" << toFile
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- 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(const std::string& fromFile)
- {
- // The input file does not exist and installation is not optional.
- std::ostringstream e;
- e << this->Name << " cannot find \"" << fromFile
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- this->Status.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->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
- const char* default_dir_install_permissions = this->Makefile->GetDefinition(
- "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
- if (default_dir_install_permissions && *default_dir_install_permissions) {
- std::vector<std::string> items;
- cmExpandList(default_dir_install_permissions, items);
- for (const auto& 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(const std::string& fromFile,
- const std::string& 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);
- if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
- std::ostringstream e;
- e << this->Name << " cannot create symlink \"" << toFile
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- this->Status.SetError(e.str());
- return false;
- }
- }
- fromFile = newFromFile;
- toFile = cmStrCat(toFilePath, "/", symlinkTarget);
- }
- return true;
- }
- 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
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- 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.
- if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
- std::ostringstream e;
- e << this->Name << " cannot duplicate symlink \"" << fromFile
- << "\" at \"" << toFile
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- this->Status.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.DifferS(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 << "\": " << cmSystemTools::GetLastSystemError() << ".";
- 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);
- }
- if (!cmFileTimes::Copy(fromFile, toFile)) {
- std::ostringstream e;
- e << this->Name << " cannot set modification time on \"" << toFile
- << "\": " << cmSystemTools::GetLastSystemError() << ".";
- 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(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->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 = static_cast<unsigned long>(dir.GetNumberOfFiles());
- for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
- if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
- strcmp(dir.GetFile(fileNum), "..") == 0)) {
- std::string fromPath = 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);
- }
|