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 <winerror.h>
20 # include "cmsys/FStream.hxx"
28 using namespace cmFSPermissions;
30 cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
32 , Makefile(&status.GetMakefile())
37 cmFileCopier::~cmFileCopier() = default;
39 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
40 const std::string& file)
42 // Match rules are case-insensitive on some platforms.
43 #if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
44 const std::string file_to_match = cmSystemTools::LowerCase(file);
46 const std::string& file_to_match = file;
49 // Collect properties from all matching rules.
51 MatchProperties result;
52 for (MatchRule& mr : this->MatchRules) {
53 if (mr.Regex.find(file_to_match)) {
55 result.Exclude |= mr.Properties.Exclude;
56 result.Permissions |= mr.Properties.Permissions;
59 if (!matched && !this->MatchlessFiles) {
60 result.Exclude = !cmSystemTools::FileIsDirectory(file);
65 bool cmFileCopier::SetPermissions(const std::string& toFile,
70 if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
71 // Store the mode in an NTFS alternate stream.
72 std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
74 // Writing to an NTFS alternate stream changes the modification
75 // time, so we need to save and restore its original value.
76 cmFileTimes file_time_orig(toFile);
78 cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
79 if (permissionStream) {
80 permissionStream << std::oct << permissions << std::endl;
82 permissionStream.close();
84 file_time_orig.Store(toFile);
88 if (!cmSystemTools::SetPermissions(toFile, permissions)) {
90 e << this->Name << " cannot set permissions on \"" << toFile
91 << "\": " << cmSystemTools::GetLastSystemError() << ".";
92 this->Status.SetError(e.str());
99 // Translate an argument to a permissions bit.
100 bool cmFileCopier::CheckPermissions(std::string const& arg,
103 if (!cmFSPermissions::stringToModeT(arg, permissions)) {
104 std::ostringstream e;
105 e << this->Name << " given invalid permission \"" << arg << "\".";
106 this->Status.SetError(e.str());
112 std::string const& cmFileCopier::ToName(std::string const& fromName)
117 bool cmFileCopier::ReportMissing(const std::string& fromFile)
119 // The input file does not exist and installation is not optional.
120 std::ostringstream e;
121 e << this->Name << " cannot find \"" << fromFile
122 << "\": " << cmSystemTools::GetLastSystemError() << ".";
123 this->Status.SetError(e.str());
127 void cmFileCopier::NotBeforeMatch(std::string const& arg)
129 std::ostringstream e;
130 e << "option " << arg << " may not appear before PATTERN or REGEX.";
131 this->Status.SetError(e.str());
132 this->Doing = DoingError;
135 void cmFileCopier::NotAfterMatch(std::string const& arg)
137 std::ostringstream e;
138 e << "option " << arg << " may not appear after PATTERN or REGEX.";
139 this->Status.SetError(e.str());
140 this->Doing = DoingError;
143 void cmFileCopier::DefaultFilePermissions()
145 // Use read/write permissions.
146 this->FilePermissions = 0;
147 this->FilePermissions |= mode_owner_read;
148 this->FilePermissions |= mode_owner_write;
149 this->FilePermissions |= mode_group_read;
150 this->FilePermissions |= mode_world_read;
153 void cmFileCopier::DefaultDirectoryPermissions()
155 // Use read/write/executable permissions.
156 this->DirPermissions = 0;
157 this->DirPermissions |= mode_owner_read;
158 this->DirPermissions |= mode_owner_write;
159 this->DirPermissions |= mode_owner_execute;
160 this->DirPermissions |= mode_group_read;
161 this->DirPermissions |= mode_group_execute;
162 this->DirPermissions |= mode_world_read;
163 this->DirPermissions |= mode_world_execute;
166 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
168 // check if default dir creation permissions were set
169 cmValue default_dir_install_permissions = this->Makefile->GetDefinition(
170 "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
171 if (cmNonempty(default_dir_install_permissions)) {
172 std::vector<std::string> items =
173 cmExpandedList(*default_dir_install_permissions);
174 for (const auto& arg : items) {
175 if (!this->CheckPermissions(arg, **mode)) {
176 this->Status.SetError(
177 " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable.");
188 bool cmFileCopier::Parse(std::vector<std::string> const& args)
190 this->Doing = DoingFiles;
191 for (unsigned int i = 1; i < args.size(); ++i) {
192 // Check this argument.
193 if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
194 std::ostringstream e;
195 e << "called with unknown argument \"" << args[i] << "\".";
196 this->Status.SetError(e.str());
200 // Quit if an argument is invalid.
201 if (this->Doing == DoingError) {
206 // Require a destination.
207 if (this->Destination.empty()) {
208 std::ostringstream e;
209 e << this->Name << " given no DESTINATION";
210 this->Status.SetError(e.str());
214 // If file permissions were not specified set default permissions.
215 if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
216 this->DefaultFilePermissions();
219 // If directory permissions were not specified set default permissions.
220 if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
221 this->DefaultDirectoryPermissions();
227 bool cmFileCopier::CheckKeyword(std::string const& arg)
229 if (arg == "DESTINATION") {
230 if (this->CurrentMatchRule) {
231 this->NotAfterMatch(arg);
233 this->Doing = DoingDestination;
235 } else if (arg == "FILES_FROM_DIR") {
236 if (this->CurrentMatchRule) {
237 this->NotAfterMatch(arg);
239 this->Doing = DoingFilesFromDir;
241 } else if (arg == "PATTERN") {
242 this->Doing = DoingPattern;
243 } else if (arg == "REGEX") {
244 this->Doing = DoingRegex;
245 } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
246 this->FollowSymlinkChain = true;
247 this->Doing = DoingNone;
248 } else if (arg == "EXCLUDE") {
249 // Add this property to the current match rule.
250 if (this->CurrentMatchRule) {
251 this->CurrentMatchRule->Properties.Exclude = true;
252 this->Doing = DoingNone;
254 this->NotBeforeMatch(arg);
256 } else if (arg == "PERMISSIONS") {
257 if (this->CurrentMatchRule) {
258 this->Doing = DoingPermissionsMatch;
260 this->NotBeforeMatch(arg);
262 } else if (arg == "FILE_PERMISSIONS") {
263 if (this->CurrentMatchRule) {
264 this->NotAfterMatch(arg);
266 this->Doing = DoingPermissionsFile;
267 this->UseGivenPermissionsFile = true;
269 } else if (arg == "DIRECTORY_PERMISSIONS") {
270 if (this->CurrentMatchRule) {
271 this->NotAfterMatch(arg);
273 this->Doing = DoingPermissionsDir;
274 this->UseGivenPermissionsDir = true;
276 } else if (arg == "USE_SOURCE_PERMISSIONS") {
277 if (this->CurrentMatchRule) {
278 this->NotAfterMatch(arg);
280 this->Doing = DoingNone;
281 this->UseSourcePermissions = true;
283 } else if (arg == "NO_SOURCE_PERMISSIONS") {
284 if (this->CurrentMatchRule) {
285 this->NotAfterMatch(arg);
287 this->Doing = DoingNone;
288 this->UseSourcePermissions = false;
290 } else if (arg == "FILES_MATCHING") {
291 if (this->CurrentMatchRule) {
292 this->NotAfterMatch(arg);
294 this->Doing = DoingNone;
295 this->MatchlessFiles = false;
303 bool cmFileCopier::CheckValue(std::string const& arg)
305 switch (this->Doing) {
307 this->Files.push_back(arg);
309 case DoingDestination:
310 if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
311 this->Destination = arg;
314 cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
316 this->Doing = DoingNone;
318 case DoingFilesFromDir:
319 if (cmSystemTools::FileIsFullPath(arg)) {
320 this->FilesFromDir = arg;
323 cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
325 cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
326 this->Doing = DoingNone;
329 // Convert the pattern to a regular expression. Require a
330 // leading slash and trailing end-of-string in the matched
331 // string to make sure the pattern matches only whole file
334 cmStrCat('/', cmsys::Glob::PatternToRegex(arg, false), '$');
335 this->MatchRules.emplace_back(regex);
336 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
337 if (this->CurrentMatchRule->Regex.is_valid()) {
338 this->Doing = DoingNone;
340 std::ostringstream e;
341 e << "could not compile PATTERN \"" << arg << "\".";
342 this->Status.SetError(e.str());
343 this->Doing = DoingError;
347 this->MatchRules.emplace_back(arg);
348 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
349 if (this->CurrentMatchRule->Regex.is_valid()) {
350 this->Doing = DoingNone;
352 std::ostringstream e;
353 e << "could not compile REGEX \"" << arg << "\".";
354 this->Status.SetError(e.str());
355 this->Doing = DoingError;
358 case DoingPermissionsFile:
359 if (!this->CheckPermissions(arg, this->FilePermissions)) {
360 this->Doing = DoingError;
363 case DoingPermissionsDir:
364 if (!this->CheckPermissions(arg, this->DirPermissions)) {
365 this->Doing = DoingError;
368 case DoingPermissionsMatch:
369 if (!this->CheckPermissions(
370 arg, this->CurrentMatchRule->Properties.Permissions)) {
371 this->Doing = DoingError;
380 bool cmFileCopier::Run(std::vector<std::string> const& args)
382 if (!this->Parse(args)) {
386 for (std::string const& f : this->Files) {
388 if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
389 if (!this->FilesFromDir.empty()) {
390 file = this->FilesFromDir;
392 file = this->Makefile->GetCurrentSourceDirectory();
396 } else if (!this->FilesFromDir.empty()) {
397 this->Status.SetError("option FILES_FROM_DIR requires all files "
398 "to be specified as relative paths.");
404 // Split the input file into its directory and name components.
405 std::vector<std::string> fromPathComponents;
406 cmSystemTools::SplitPath(file, fromPathComponents);
407 std::string fromName = *(fromPathComponents.end() - 1);
408 std::string fromDir = cmSystemTools::JoinPath(
409 fromPathComponents.begin(), fromPathComponents.end() - 1);
411 // Compute the full path to the destination file.
412 std::string toFile = this->Destination;
413 if (!this->FilesFromDir.empty()) {
414 std::string dir = cmSystemTools::GetFilenamePath(f);
420 std::string const& toName = this->ToName(fromName);
421 if (!toName.empty()) {
426 // Construct the full path to the source file. The file name may
427 // have been changed above.
428 std::string fromFile = fromDir;
429 if (!fromName.empty()) {
431 fromFile += fromName;
434 if (!this->Install(fromFile, toFile)) {
441 bool cmFileCopier::Install(const std::string& fromFile,
442 const std::string& toFile)
444 if (fromFile.empty()) {
445 this->Status.SetError(
446 "INSTALL encountered an empty string input file name.");
450 // Collect any properties matching this file name.
451 MatchProperties match_properties = this->CollectMatchProperties(fromFile);
453 // Skip the file if it is excluded.
454 if (match_properties.Exclude) {
458 if (cmSystemTools::SameFile(fromFile, toFile)) {
462 std::string newFromFile = fromFile;
463 std::string newToFile = toFile;
465 if (this->FollowSymlinkChain &&
466 !this->InstallSymlinkChain(newFromFile, newToFile)) {
470 if (cmSystemTools::FileIsSymlink(newFromFile)) {
471 return this->InstallSymlink(newFromFile, newToFile);
473 if (cmSystemTools::FileIsDirectory(newFromFile)) {
474 return this->InstallDirectory(newFromFile, newToFile, match_properties);
476 if (cmSystemTools::FileExists(newFromFile)) {
477 return this->InstallFile(newFromFile, newToFile, match_properties);
479 return this->ReportMissing(newFromFile);
482 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
485 std::string newFromFile;
486 std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
487 while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
488 if (!cmSystemTools::FileIsFullPath(newFromFile)) {
489 std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
490 newFromFile = cmStrCat(fromFilePath, "/", newFromFile);
493 std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
497 std::string oldSymlinkTarget;
498 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
499 if (symlinkTarget == oldSymlinkTarget) {
505 this->ReportCopy(toFile, TypeLink, copy);
508 cmSystemTools::RemoveFile(toFile);
509 cmSystemTools::MakeDirectory(toFilePath);
511 cmsys::Status status =
512 cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
514 std::string e = cmStrCat(this->Name, " cannot create symlink\n ",
515 toFile, "\nbecause: ", status.GetString());
516 this->Status.SetError(e);
521 fromFile = newFromFile;
522 toFile = cmStrCat(toFilePath, "/", symlinkTarget);
528 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
529 const std::string& toFile)
531 // Read the original symlink.
532 std::string symlinkTarget;
533 if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
534 std::ostringstream e;
535 e << this->Name << " cannot read symlink \"" << fromFile
536 << "\" to duplicate at \"" << toFile
537 << "\": " << cmSystemTools::GetLastSystemError() << ".";
538 this->Status.SetError(e.str());
542 // Compare the symlink value to that at the destination if not
543 // always installing.
546 std::string oldSymlinkTarget;
547 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
548 if (symlinkTarget == oldSymlinkTarget) {
554 // Inform the user about this file installation.
555 this->ReportCopy(toFile, TypeLink, copy);
558 // Remove the destination file so we can always create the symlink.
559 cmSystemTools::RemoveFile(toFile);
561 // Create destination directory if it doesn't exist
562 cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
564 // Create the symlink.
565 cmsys::Status status =
566 cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
569 bool const errorFileExists = status.GetWindows() == ERROR_FILE_EXISTS;
571 bool const errorFileExists = status.GetPOSIX() == EEXIST;
574 if (errorFileExists && cmSystemTools::FileIsDirectory(toFile)) {
575 reason = "A directory already exists at that location";
577 reason = status.GetString();
580 cmStrCat(this->Name, " cannot duplicate symlink\n ", fromFile,
581 "\nat\n ", toFile, "\nbecause: ", reason);
582 this->Status.SetError(e);
590 bool cmFileCopier::InstallFile(const std::string& fromFile,
591 const std::string& toFile,
592 MatchProperties match_properties)
594 // Determine whether we will copy the file.
597 // If both files exist with the same time do not copy.
598 if (!this->FileTimes.DifferS(fromFile, toFile)) {
603 // Inform the user about this file installation.
604 this->ReportCopy(toFile, TypeFile, copy);
607 if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
608 std::ostringstream e;
609 e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
610 << toFile << "\": " << cmSystemTools::GetLastSystemError() << ".";
611 this->Status.SetError(e.str());
615 // Set the file modification time of the destination file.
616 if (copy && !this->Always) {
617 // Add write permission so we can set the file time.
618 // Permissions are set unconditionally below anyway.
620 if (cmSystemTools::GetPermissions(toFile, perm)) {
621 cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
623 if (!cmFileTimes::Copy(fromFile, toFile)) {
624 std::ostringstream e;
625 e << this->Name << " cannot set modification time on \"" << toFile
626 << "\": " << cmSystemTools::GetLastSystemError() << ".";
627 this->Status.SetError(e.str());
632 // Set permissions of the destination file.
634 (match_properties.Permissions ? match_properties.Permissions
635 : this->FilePermissions);
637 // No permissions were explicitly provided but the user requested
638 // that the source file permissions be used.
639 cmSystemTools::GetPermissions(fromFile, permissions);
641 return this->SetPermissions(toFile, permissions);
644 bool cmFileCopier::InstallDirectory(const std::string& source,
645 const std::string& destination,
646 MatchProperties match_properties)
648 // Inform the user about this directory installation.
649 this->ReportCopy(destination, TypeDir,
650 !( // Report "Up-to-date:" for existing directories,
651 // but not symlinks to them.
652 cmSystemTools::FileIsDirectory(destination) &&
653 !cmSystemTools::FileIsSymlink(destination)));
655 // check if default dir creation permissions were set
656 mode_t default_dir_mode_v = 0;
657 mode_t* default_dir_mode = &default_dir_mode_v;
658 if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
662 // Make sure the destination directory exists.
663 if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
664 std::ostringstream e;
665 e << this->Name << " cannot make directory \"" << destination
666 << "\": " << cmSystemTools::GetLastSystemError() << ".";
667 this->Status.SetError(e.str());
671 // Compute the requested permissions for the destination directory.
673 (match_properties.Permissions ? match_properties.Permissions
674 : this->DirPermissions);
676 // No permissions were explicitly provided but the user requested
677 // that the source directory permissions be used.
678 cmSystemTools::GetPermissions(source, permissions);
681 // Compute the set of permissions required on this directory to
682 // recursively install files and subdirectories safely.
683 mode_t required_permissions =
684 mode_owner_read | mode_owner_write | mode_owner_execute;
686 // If the required permissions are specified it is safe to set the
687 // final permissions now. Otherwise we must add the required
688 // permissions temporarily during file installation.
689 mode_t permissions_before = 0;
690 mode_t permissions_after = 0;
691 if ((permissions & required_permissions) == required_permissions) {
692 permissions_before = permissions;
694 permissions_before = permissions | required_permissions;
695 permissions_after = permissions;
698 // Set the required permissions of the destination directory.
699 if (!this->SetPermissions(destination, permissions_before)) {
703 // Load the directory contents to traverse it recursively.
704 cmsys::Directory dir;
705 if (!source.empty()) {
708 unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
709 for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
710 if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
711 strcmp(dir.GetFile(fileNum), "..") == 0)) {
712 std::string fromPath = cmStrCat(source, '/', dir.GetFile(fileNum));
713 std::string toPath = cmStrCat(destination, '/', dir.GetFile(fileNum));
714 if (!this->Install(fromPath, toPath)) {
720 // Set the requested permissions of the destination directory.
721 return this->SetPermissions(destination, permissions_after);