1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #include "cmFileCopier.h"
6 #include "cmsys/Directory.hxx"
7 #include "cmsys/Glob.hxx"
9 #include "cmExecutionStatus.h"
10 #include "cmFSPermissions.h"
11 #include "cmFileTimes.h"
12 #include "cmMakefile.h"
13 #include "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
18 # include "cmsys/FStream.hxx"
24 using namespace cmFSPermissions;
26 cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
28 , Makefile(&status.GetMakefile())
33 cmFileCopier::~cmFileCopier() = default;
35 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
36 const std::string& file)
38 // Match rules are case-insensitive on some platforms.
39 #if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
40 const std::string file_to_match = cmSystemTools::LowerCase(file);
42 const std::string& file_to_match = file;
45 // Collect properties from all matching rules.
47 MatchProperties result;
48 for (MatchRule& mr : this->MatchRules) {
49 if (mr.Regex.find(file_to_match)) {
51 result.Exclude |= mr.Properties.Exclude;
52 result.Permissions |= mr.Properties.Permissions;
55 if (!matched && !this->MatchlessFiles) {
56 result.Exclude = !cmSystemTools::FileIsDirectory(file);
61 bool cmFileCopier::SetPermissions(const std::string& toFile,
66 if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
67 // Store the mode in an NTFS alternate stream.
68 std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
70 // Writing to an NTFS alternate stream changes the modification
71 // time, so we need to save and restore its original value.
72 cmFileTimes file_time_orig(toFile);
74 cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
75 if (permissionStream) {
76 permissionStream << std::oct << permissions << std::endl;
78 permissionStream.close();
80 file_time_orig.Store(toFile);
84 if (!cmSystemTools::SetPermissions(toFile, permissions)) {
86 e << this->Name << " cannot set permissions on \"" << toFile
87 << "\": " << cmSystemTools::GetLastSystemError() << ".";
88 this->Status.SetError(e.str());
95 // Translate an argument to a permissions bit.
96 bool cmFileCopier::CheckPermissions(std::string const& arg,
99 if (!cmFSPermissions::stringToModeT(arg, permissions)) {
100 std::ostringstream e;
101 e << this->Name << " given invalid permission \"" << arg << "\".";
102 this->Status.SetError(e.str());
108 std::string const& cmFileCopier::ToName(std::string const& fromName)
113 bool cmFileCopier::ReportMissing(const std::string& fromFile)
115 // The input file does not exist and installation is not optional.
116 std::ostringstream e;
117 e << this->Name << " cannot find \"" << fromFile
118 << "\": " << cmSystemTools::GetLastSystemError() << ".";
119 this->Status.SetError(e.str());
123 void cmFileCopier::NotBeforeMatch(std::string const& arg)
125 std::ostringstream e;
126 e << "option " << arg << " may not appear before PATTERN or REGEX.";
127 this->Status.SetError(e.str());
128 this->Doing = DoingError;
131 void cmFileCopier::NotAfterMatch(std::string const& arg)
133 std::ostringstream e;
134 e << "option " << arg << " may not appear after PATTERN or REGEX.";
135 this->Status.SetError(e.str());
136 this->Doing = DoingError;
139 void cmFileCopier::DefaultFilePermissions()
141 // Use read/write permissions.
142 this->FilePermissions = 0;
143 this->FilePermissions |= mode_owner_read;
144 this->FilePermissions |= mode_owner_write;
145 this->FilePermissions |= mode_group_read;
146 this->FilePermissions |= mode_world_read;
149 void cmFileCopier::DefaultDirectoryPermissions()
151 // Use read/write/executable permissions.
152 this->DirPermissions = 0;
153 this->DirPermissions |= mode_owner_read;
154 this->DirPermissions |= mode_owner_write;
155 this->DirPermissions |= mode_owner_execute;
156 this->DirPermissions |= mode_group_read;
157 this->DirPermissions |= mode_group_execute;
158 this->DirPermissions |= mode_world_read;
159 this->DirPermissions |= mode_world_execute;
162 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
164 // check if default dir creation permissions were set
165 cmValue default_dir_install_permissions = this->Makefile->GetDefinition(
166 "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
167 if (cmNonempty(default_dir_install_permissions)) {
168 std::vector<std::string> items =
169 cmExpandedList(*default_dir_install_permissions);
170 for (const auto& arg : items) {
171 if (!this->CheckPermissions(arg, **mode)) {
172 this->Status.SetError(
173 " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable.");
184 bool cmFileCopier::Parse(std::vector<std::string> const& args)
186 this->Doing = DoingFiles;
187 for (unsigned int i = 1; i < args.size(); ++i) {
188 // Check this argument.
189 if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
190 std::ostringstream e;
191 e << "called with unknown argument \"" << args[i] << "\".";
192 this->Status.SetError(e.str());
196 // Quit if an argument is invalid.
197 if (this->Doing == DoingError) {
202 // Require a destination.
203 if (this->Destination.empty()) {
204 std::ostringstream e;
205 e << this->Name << " given no DESTINATION";
206 this->Status.SetError(e.str());
210 // If file permissions were not specified set default permissions.
211 if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
212 this->DefaultFilePermissions();
215 // If directory permissions were not specified set default permissions.
216 if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
217 this->DefaultDirectoryPermissions();
223 bool cmFileCopier::CheckKeyword(std::string const& arg)
225 if (arg == "DESTINATION") {
226 if (this->CurrentMatchRule) {
227 this->NotAfterMatch(arg);
229 this->Doing = DoingDestination;
231 } else if (arg == "FILES_FROM_DIR") {
232 if (this->CurrentMatchRule) {
233 this->NotAfterMatch(arg);
235 this->Doing = DoingFilesFromDir;
237 } else if (arg == "PATTERN") {
238 this->Doing = DoingPattern;
239 } else if (arg == "REGEX") {
240 this->Doing = DoingRegex;
241 } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
242 this->FollowSymlinkChain = true;
243 this->Doing = DoingNone;
244 } else if (arg == "EXCLUDE") {
245 // Add this property to the current match rule.
246 if (this->CurrentMatchRule) {
247 this->CurrentMatchRule->Properties.Exclude = true;
248 this->Doing = DoingNone;
250 this->NotBeforeMatch(arg);
252 } else if (arg == "PERMISSIONS") {
253 if (this->CurrentMatchRule) {
254 this->Doing = DoingPermissionsMatch;
256 this->NotBeforeMatch(arg);
258 } else if (arg == "FILE_PERMISSIONS") {
259 if (this->CurrentMatchRule) {
260 this->NotAfterMatch(arg);
262 this->Doing = DoingPermissionsFile;
263 this->UseGivenPermissionsFile = true;
265 } else if (arg == "DIRECTORY_PERMISSIONS") {
266 if (this->CurrentMatchRule) {
267 this->NotAfterMatch(arg);
269 this->Doing = DoingPermissionsDir;
270 this->UseGivenPermissionsDir = true;
272 } else if (arg == "USE_SOURCE_PERMISSIONS") {
273 if (this->CurrentMatchRule) {
274 this->NotAfterMatch(arg);
276 this->Doing = DoingNone;
277 this->UseSourcePermissions = true;
279 } else if (arg == "NO_SOURCE_PERMISSIONS") {
280 if (this->CurrentMatchRule) {
281 this->NotAfterMatch(arg);
283 this->Doing = DoingNone;
284 this->UseSourcePermissions = false;
286 } else if (arg == "FILES_MATCHING") {
287 if (this->CurrentMatchRule) {
288 this->NotAfterMatch(arg);
290 this->Doing = DoingNone;
291 this->MatchlessFiles = false;
299 bool cmFileCopier::CheckValue(std::string const& arg)
301 switch (this->Doing) {
303 this->Files.push_back(arg);
305 case DoingDestination:
306 if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
307 this->Destination = arg;
310 cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
312 this->Doing = DoingNone;
314 case DoingFilesFromDir:
315 if (cmSystemTools::FileIsFullPath(arg)) {
316 this->FilesFromDir = arg;
319 cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
321 cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
322 this->Doing = DoingNone;
325 // Convert the pattern to a regular expression. Require a
326 // leading slash and trailing end-of-string in the matched
327 // string to make sure the pattern matches only whole file
330 cmStrCat('/', cmsys::Glob::PatternToRegex(arg, false), '$');
331 this->MatchRules.emplace_back(regex);
332 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
333 if (this->CurrentMatchRule->Regex.is_valid()) {
334 this->Doing = DoingNone;
336 std::ostringstream e;
337 e << "could not compile PATTERN \"" << arg << "\".";
338 this->Status.SetError(e.str());
339 this->Doing = DoingError;
343 this->MatchRules.emplace_back(arg);
344 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
345 if (this->CurrentMatchRule->Regex.is_valid()) {
346 this->Doing = DoingNone;
348 std::ostringstream e;
349 e << "could not compile REGEX \"" << arg << "\".";
350 this->Status.SetError(e.str());
351 this->Doing = DoingError;
354 case DoingPermissionsFile:
355 if (!this->CheckPermissions(arg, this->FilePermissions)) {
356 this->Doing = DoingError;
359 case DoingPermissionsDir:
360 if (!this->CheckPermissions(arg, this->DirPermissions)) {
361 this->Doing = DoingError;
364 case DoingPermissionsMatch:
365 if (!this->CheckPermissions(
366 arg, this->CurrentMatchRule->Properties.Permissions)) {
367 this->Doing = DoingError;
376 bool cmFileCopier::Run(std::vector<std::string> const& args)
378 if (!this->Parse(args)) {
382 for (std::string const& f : this->Files) {
384 if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
385 if (!this->FilesFromDir.empty()) {
386 file = this->FilesFromDir;
388 file = this->Makefile->GetCurrentSourceDirectory();
392 } else if (!this->FilesFromDir.empty()) {
393 this->Status.SetError("option FILES_FROM_DIR requires all files "
394 "to be specified as relative paths.");
400 // Split the input file into its directory and name components.
401 std::vector<std::string> fromPathComponents;
402 cmSystemTools::SplitPath(file, fromPathComponents);
403 std::string fromName = *(fromPathComponents.end() - 1);
404 std::string fromDir = cmSystemTools::JoinPath(
405 fromPathComponents.begin(), fromPathComponents.end() - 1);
407 // Compute the full path to the destination file.
408 std::string toFile = this->Destination;
409 if (!this->FilesFromDir.empty()) {
410 std::string dir = cmSystemTools::GetFilenamePath(f);
416 std::string const& toName = this->ToName(fromName);
417 if (!toName.empty()) {
422 // Construct the full path to the source file. The file name may
423 // have been changed above.
424 std::string fromFile = fromDir;
425 if (!fromName.empty()) {
427 fromFile += fromName;
430 if (!this->Install(fromFile, toFile)) {
437 bool cmFileCopier::Install(const std::string& fromFile,
438 const std::string& toFile)
440 if (fromFile.empty()) {
441 this->Status.SetError(
442 "INSTALL encountered an empty string input file name.");
446 // Collect any properties matching this file name.
447 MatchProperties match_properties = this->CollectMatchProperties(fromFile);
449 // Skip the file if it is excluded.
450 if (match_properties.Exclude) {
454 if (cmSystemTools::SameFile(fromFile, toFile)) {
458 std::string newFromFile = fromFile;
459 std::string newToFile = toFile;
461 if (this->FollowSymlinkChain &&
462 !this->InstallSymlinkChain(newFromFile, newToFile)) {
466 if (cmSystemTools::FileIsSymlink(newFromFile)) {
467 return this->InstallSymlink(newFromFile, newToFile);
469 if (cmSystemTools::FileIsDirectory(newFromFile)) {
470 return this->InstallDirectory(newFromFile, newToFile, match_properties);
472 if (cmSystemTools::FileExists(newFromFile)) {
473 return this->InstallFile(newFromFile, newToFile, match_properties);
475 return this->ReportMissing(newFromFile);
478 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
481 std::string newFromFile;
482 std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
483 while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
484 if (!cmSystemTools::FileIsFullPath(newFromFile)) {
485 std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
486 newFromFile = cmStrCat(fromFilePath, "/", newFromFile);
489 std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
493 std::string oldSymlinkTarget;
494 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
495 if (symlinkTarget == oldSymlinkTarget) {
501 this->ReportCopy(toFile, TypeLink, copy);
504 cmSystemTools::RemoveFile(toFile);
505 cmSystemTools::MakeDirectory(toFilePath);
507 if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
508 std::ostringstream e;
509 e << this->Name << " cannot create symlink \"" << toFile
510 << "\": " << cmSystemTools::GetLastSystemError() << ".";
511 this->Status.SetError(e.str());
516 fromFile = newFromFile;
517 toFile = cmStrCat(toFilePath, "/", symlinkTarget);
523 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
524 const std::string& toFile)
526 // Read the original symlink.
527 std::string symlinkTarget;
528 if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
529 std::ostringstream e;
530 e << this->Name << " cannot read symlink \"" << fromFile
531 << "\" to duplicate at \"" << toFile
532 << "\": " << cmSystemTools::GetLastSystemError() << ".";
533 this->Status.SetError(e.str());
537 // Compare the symlink value to that at the destination if not
538 // always installing.
541 std::string oldSymlinkTarget;
542 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
543 if (symlinkTarget == oldSymlinkTarget) {
549 // Inform the user about this file installation.
550 this->ReportCopy(toFile, TypeLink, copy);
553 // Remove the destination file so we can always create the symlink.
554 cmSystemTools::RemoveFile(toFile);
556 // Create destination directory if it doesn't exist
557 cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
559 // Create the symlink.
560 if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
561 std::ostringstream e;
562 e << this->Name << " cannot duplicate symlink \"" << fromFile
563 << "\" at \"" << toFile
564 << "\": " << cmSystemTools::GetLastSystemError() << ".";
565 this->Status.SetError(e.str());
573 bool cmFileCopier::InstallFile(const std::string& fromFile,
574 const std::string& toFile,
575 MatchProperties match_properties)
577 // Determine whether we will copy the file.
580 // If both files exist with the same time do not copy.
581 if (!this->FileTimes.DifferS(fromFile, toFile)) {
586 // Inform the user about this file installation.
587 this->ReportCopy(toFile, TypeFile, copy);
590 if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
591 std::ostringstream e;
592 e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
593 << toFile << "\": " << cmSystemTools::GetLastSystemError() << ".";
594 this->Status.SetError(e.str());
598 // Set the file modification time of the destination file.
599 if (copy && !this->Always) {
600 // Add write permission so we can set the file time.
601 // Permissions are set unconditionally below anyway.
603 if (cmSystemTools::GetPermissions(toFile, perm)) {
604 cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
606 if (!cmFileTimes::Copy(fromFile, toFile)) {
607 std::ostringstream e;
608 e << this->Name << " cannot set modification time on \"" << toFile
609 << "\": " << cmSystemTools::GetLastSystemError() << ".";
610 this->Status.SetError(e.str());
615 // Set permissions of the destination file.
617 (match_properties.Permissions ? match_properties.Permissions
618 : this->FilePermissions);
620 // No permissions were explicitly provided but the user requested
621 // that the source file permissions be used.
622 cmSystemTools::GetPermissions(fromFile, permissions);
624 return this->SetPermissions(toFile, permissions);
627 bool cmFileCopier::InstallDirectory(const std::string& source,
628 const std::string& destination,
629 MatchProperties match_properties)
631 // Inform the user about this directory installation.
632 this->ReportCopy(destination, TypeDir,
633 !cmSystemTools::FileIsDirectory(destination));
635 // check if default dir creation permissions were set
636 mode_t default_dir_mode_v = 0;
637 mode_t* default_dir_mode = &default_dir_mode_v;
638 if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
642 // Make sure the destination directory exists.
643 if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
644 std::ostringstream e;
645 e << this->Name << " cannot make directory \"" << destination
646 << "\": " << cmSystemTools::GetLastSystemError() << ".";
647 this->Status.SetError(e.str());
651 // Compute the requested permissions for the destination directory.
653 (match_properties.Permissions ? match_properties.Permissions
654 : this->DirPermissions);
656 // No permissions were explicitly provided but the user requested
657 // that the source directory permissions be used.
658 cmSystemTools::GetPermissions(source, permissions);
661 // Compute the set of permissions required on this directory to
662 // recursively install files and subdirectories safely.
663 mode_t required_permissions =
664 mode_owner_read | mode_owner_write | mode_owner_execute;
666 // If the required permissions are specified it is safe to set the
667 // final permissions now. Otherwise we must add the required
668 // permissions temporarily during file installation.
669 mode_t permissions_before = 0;
670 mode_t permissions_after = 0;
671 if ((permissions & required_permissions) == required_permissions) {
672 permissions_before = permissions;
674 permissions_before = permissions | required_permissions;
675 permissions_after = permissions;
678 // Set the required permissions of the destination directory.
679 if (!this->SetPermissions(destination, permissions_before)) {
683 // Load the directory contents to traverse it recursively.
684 cmsys::Directory dir;
685 if (!source.empty()) {
688 unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
689 for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
690 if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
691 strcmp(dir.GetFile(fileNum), "..") == 0)) {
692 std::string fromPath = cmStrCat(source, '/', dir.GetFile(fileNum));
693 std::string toPath = cmStrCat(destination, '/', dir.GetFile(fileNum));
694 if (!this->Install(fromPath, toPath)) {
700 // Set the requested permissions of the destination directory.
701 return this->SetPermissions(destination, permissions_after);