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 "cmProperty.h"
14 #include "cmStringAlgorithms.h"
15 #include "cmSystemTools.h"
18 # include "cmsys/FStream.hxx"
24 using namespace cmFSPermissions;
26 cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
28 , Makefile(&status.GetMakefile())
31 , MatchlessFiles(true)
34 , CurrentMatchRule(nullptr)
35 , UseGivenPermissionsFile(false)
36 , UseGivenPermissionsDir(false)
37 , UseSourcePermissions(true)
38 , FollowSymlinkChain(false)
43 cmFileCopier::~cmFileCopier() = default;
45 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
46 const std::string& file)
48 // Match rules are case-insensitive on some platforms.
49 #if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
50 const std::string file_to_match = cmSystemTools::LowerCase(file);
52 const std::string& file_to_match = file;
55 // Collect properties from all matching rules.
57 MatchProperties result;
58 for (MatchRule& mr : this->MatchRules) {
59 if (mr.Regex.find(file_to_match)) {
61 result.Exclude |= mr.Properties.Exclude;
62 result.Permissions |= mr.Properties.Permissions;
65 if (!matched && !this->MatchlessFiles) {
66 result.Exclude = !cmSystemTools::FileIsDirectory(file);
71 bool cmFileCopier::SetPermissions(const std::string& toFile,
76 if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
77 // Store the mode in an NTFS alternate stream.
78 std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
80 // Writing to an NTFS alternate stream changes the modification
81 // time, so we need to save and restore its original value.
82 cmFileTimes file_time_orig(toFile);
84 cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
85 if (permissionStream) {
86 permissionStream << std::oct << permissions << std::endl;
88 permissionStream.close();
90 file_time_orig.Store(toFile);
94 if (!cmSystemTools::SetPermissions(toFile, permissions)) {
96 e << this->Name << " cannot set permissions on \"" << toFile
97 << "\": " << cmSystemTools::GetLastSystemError() << ".";
98 this->Status.SetError(e.str());
105 // Translate an argument to a permissions bit.
106 bool cmFileCopier::CheckPermissions(std::string const& arg,
109 if (!cmFSPermissions::stringToModeT(arg, permissions)) {
110 std::ostringstream e;
111 e << this->Name << " given invalid permission \"" << arg << "\".";
112 this->Status.SetError(e.str());
118 std::string const& cmFileCopier::ToName(std::string const& fromName)
123 bool cmFileCopier::ReportMissing(const std::string& fromFile)
125 // The input file does not exist and installation is not optional.
126 std::ostringstream e;
127 e << this->Name << " cannot find \"" << fromFile
128 << "\": " << cmSystemTools::GetLastSystemError() << ".";
129 this->Status.SetError(e.str());
133 void cmFileCopier::NotBeforeMatch(std::string const& arg)
135 std::ostringstream e;
136 e << "option " << arg << " may not appear before PATTERN or REGEX.";
137 this->Status.SetError(e.str());
138 this->Doing = DoingError;
141 void cmFileCopier::NotAfterMatch(std::string const& arg)
143 std::ostringstream e;
144 e << "option " << arg << " may not appear after PATTERN or REGEX.";
145 this->Status.SetError(e.str());
146 this->Doing = DoingError;
149 void cmFileCopier::DefaultFilePermissions()
151 // Use read/write permissions.
152 this->FilePermissions = 0;
153 this->FilePermissions |= mode_owner_read;
154 this->FilePermissions |= mode_owner_write;
155 this->FilePermissions |= mode_group_read;
156 this->FilePermissions |= mode_world_read;
159 void cmFileCopier::DefaultDirectoryPermissions()
161 // Use read/write/executable permissions.
162 this->DirPermissions = 0;
163 this->DirPermissions |= mode_owner_read;
164 this->DirPermissions |= mode_owner_write;
165 this->DirPermissions |= mode_owner_execute;
166 this->DirPermissions |= mode_group_read;
167 this->DirPermissions |= mode_group_execute;
168 this->DirPermissions |= mode_world_read;
169 this->DirPermissions |= mode_world_execute;
172 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
174 // check if default dir creation permissions were set
175 cmProp default_dir_install_permissions = this->Makefile->GetDefinition(
176 "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
177 if (cmNonempty(default_dir_install_permissions)) {
178 std::vector<std::string> items =
179 cmExpandedList(*default_dir_install_permissions);
180 for (const auto& arg : items) {
181 if (!this->CheckPermissions(arg, **mode)) {
182 this->Status.SetError(
183 " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable.");
194 bool cmFileCopier::Parse(std::vector<std::string> const& args)
196 this->Doing = DoingFiles;
197 for (unsigned int i = 1; i < args.size(); ++i) {
198 // Check this argument.
199 if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
200 std::ostringstream e;
201 e << "called with unknown argument \"" << args[i] << "\".";
202 this->Status.SetError(e.str());
206 // Quit if an argument is invalid.
207 if (this->Doing == DoingError) {
212 // Require a destination.
213 if (this->Destination.empty()) {
214 std::ostringstream e;
215 e << this->Name << " given no DESTINATION";
216 this->Status.SetError(e.str());
220 // If file permissions were not specified set default permissions.
221 if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
222 this->DefaultFilePermissions();
225 // If directory permissions were not specified set default permissions.
226 if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
227 this->DefaultDirectoryPermissions();
233 bool cmFileCopier::CheckKeyword(std::string const& arg)
235 if (arg == "DESTINATION") {
236 if (this->CurrentMatchRule) {
237 this->NotAfterMatch(arg);
239 this->Doing = DoingDestination;
241 } else if (arg == "FILES_FROM_DIR") {
242 if (this->CurrentMatchRule) {
243 this->NotAfterMatch(arg);
245 this->Doing = DoingFilesFromDir;
247 } else if (arg == "PATTERN") {
248 this->Doing = DoingPattern;
249 } else if (arg == "REGEX") {
250 this->Doing = DoingRegex;
251 } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
252 this->FollowSymlinkChain = true;
253 this->Doing = DoingNone;
254 } else if (arg == "EXCLUDE") {
255 // Add this property to the current match rule.
256 if (this->CurrentMatchRule) {
257 this->CurrentMatchRule->Properties.Exclude = true;
258 this->Doing = DoingNone;
260 this->NotBeforeMatch(arg);
262 } else if (arg == "PERMISSIONS") {
263 if (this->CurrentMatchRule) {
264 this->Doing = DoingPermissionsMatch;
266 this->NotBeforeMatch(arg);
268 } else if (arg == "FILE_PERMISSIONS") {
269 if (this->CurrentMatchRule) {
270 this->NotAfterMatch(arg);
272 this->Doing = DoingPermissionsFile;
273 this->UseGivenPermissionsFile = true;
275 } else if (arg == "DIRECTORY_PERMISSIONS") {
276 if (this->CurrentMatchRule) {
277 this->NotAfterMatch(arg);
279 this->Doing = DoingPermissionsDir;
280 this->UseGivenPermissionsDir = true;
282 } else if (arg == "USE_SOURCE_PERMISSIONS") {
283 if (this->CurrentMatchRule) {
284 this->NotAfterMatch(arg);
286 this->Doing = DoingNone;
287 this->UseSourcePermissions = true;
289 } else if (arg == "NO_SOURCE_PERMISSIONS") {
290 if (this->CurrentMatchRule) {
291 this->NotAfterMatch(arg);
293 this->Doing = DoingNone;
294 this->UseSourcePermissions = false;
296 } else if (arg == "FILES_MATCHING") {
297 if (this->CurrentMatchRule) {
298 this->NotAfterMatch(arg);
300 this->Doing = DoingNone;
301 this->MatchlessFiles = false;
309 bool cmFileCopier::CheckValue(std::string const& arg)
311 switch (this->Doing) {
313 this->Files.push_back(arg);
315 case DoingDestination:
316 if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
317 this->Destination = arg;
320 cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
322 this->Doing = DoingNone;
324 case DoingFilesFromDir:
325 if (cmSystemTools::FileIsFullPath(arg)) {
326 this->FilesFromDir = arg;
329 cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
331 cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
332 this->Doing = DoingNone;
335 // Convert the pattern to a regular expression. Require a
336 // leading slash and trailing end-of-string in the matched
337 // string to make sure the pattern matches only whole file
340 cmStrCat('/', cmsys::Glob::PatternToRegex(arg, false), '$');
341 this->MatchRules.emplace_back(regex);
342 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
343 if (this->CurrentMatchRule->Regex.is_valid()) {
344 this->Doing = DoingNone;
346 std::ostringstream e;
347 e << "could not compile PATTERN \"" << arg << "\".";
348 this->Status.SetError(e.str());
349 this->Doing = DoingError;
353 this->MatchRules.emplace_back(arg);
354 this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
355 if (this->CurrentMatchRule->Regex.is_valid()) {
356 this->Doing = DoingNone;
358 std::ostringstream e;
359 e << "could not compile REGEX \"" << arg << "\".";
360 this->Status.SetError(e.str());
361 this->Doing = DoingError;
364 case DoingPermissionsFile:
365 if (!this->CheckPermissions(arg, this->FilePermissions)) {
366 this->Doing = DoingError;
369 case DoingPermissionsDir:
370 if (!this->CheckPermissions(arg, this->DirPermissions)) {
371 this->Doing = DoingError;
374 case DoingPermissionsMatch:
375 if (!this->CheckPermissions(
376 arg, this->CurrentMatchRule->Properties.Permissions)) {
377 this->Doing = DoingError;
386 bool cmFileCopier::Run(std::vector<std::string> const& args)
388 if (!this->Parse(args)) {
392 for (std::string const& f : this->Files) {
394 if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
395 if (!this->FilesFromDir.empty()) {
396 file = this->FilesFromDir;
398 file = this->Makefile->GetCurrentSourceDirectory();
402 } else if (!this->FilesFromDir.empty()) {
403 this->Status.SetError("option FILES_FROM_DIR requires all files "
404 "to be specified as relative paths.");
410 // Split the input file into its directory and name components.
411 std::vector<std::string> fromPathComponents;
412 cmSystemTools::SplitPath(file, fromPathComponents);
413 std::string fromName = *(fromPathComponents.end() - 1);
414 std::string fromDir = cmSystemTools::JoinPath(
415 fromPathComponents.begin(), fromPathComponents.end() - 1);
417 // Compute the full path to the destination file.
418 std::string toFile = this->Destination;
419 if (!this->FilesFromDir.empty()) {
420 std::string dir = cmSystemTools::GetFilenamePath(f);
426 std::string const& toName = this->ToName(fromName);
427 if (!toName.empty()) {
432 // Construct the full path to the source file. The file name may
433 // have been changed above.
434 std::string fromFile = fromDir;
435 if (!fromName.empty()) {
437 fromFile += fromName;
440 if (!this->Install(fromFile, toFile)) {
447 bool cmFileCopier::Install(const std::string& fromFile,
448 const std::string& toFile)
450 if (fromFile.empty()) {
451 this->Status.SetError(
452 "INSTALL encountered an empty string input file name.");
456 // Collect any properties matching this file name.
457 MatchProperties match_properties = this->CollectMatchProperties(fromFile);
459 // Skip the file if it is excluded.
460 if (match_properties.Exclude) {
464 if (cmSystemTools::SameFile(fromFile, toFile)) {
468 std::string newFromFile = fromFile;
469 std::string newToFile = toFile;
471 if (this->FollowSymlinkChain &&
472 !this->InstallSymlinkChain(newFromFile, newToFile)) {
476 if (cmSystemTools::FileIsSymlink(newFromFile)) {
477 return this->InstallSymlink(newFromFile, newToFile);
479 if (cmSystemTools::FileIsDirectory(newFromFile)) {
480 return this->InstallDirectory(newFromFile, newToFile, match_properties);
482 if (cmSystemTools::FileExists(newFromFile)) {
483 return this->InstallFile(newFromFile, newToFile, match_properties);
485 return this->ReportMissing(newFromFile);
488 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
491 std::string newFromFile;
492 std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
493 while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
494 if (!cmSystemTools::FileIsFullPath(newFromFile)) {
495 std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
496 newFromFile = cmStrCat(fromFilePath, "/", newFromFile);
499 std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
503 std::string oldSymlinkTarget;
504 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
505 if (symlinkTarget == oldSymlinkTarget) {
511 this->ReportCopy(toFile, TypeLink, copy);
514 cmSystemTools::RemoveFile(toFile);
515 cmSystemTools::MakeDirectory(toFilePath);
517 if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
518 std::ostringstream e;
519 e << this->Name << " cannot create symlink \"" << toFile
520 << "\": " << cmSystemTools::GetLastSystemError() << ".";
521 this->Status.SetError(e.str());
526 fromFile = newFromFile;
527 toFile = cmStrCat(toFilePath, "/", symlinkTarget);
533 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
534 const std::string& toFile)
536 // Read the original symlink.
537 std::string symlinkTarget;
538 if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
539 std::ostringstream e;
540 e << this->Name << " cannot read symlink \"" << fromFile
541 << "\" to duplicate at \"" << toFile
542 << "\": " << cmSystemTools::GetLastSystemError() << ".";
543 this->Status.SetError(e.str());
547 // Compare the symlink value to that at the destination if not
548 // always installing.
551 std::string oldSymlinkTarget;
552 if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
553 if (symlinkTarget == oldSymlinkTarget) {
559 // Inform the user about this file installation.
560 this->ReportCopy(toFile, TypeLink, copy);
563 // Remove the destination file so we can always create the symlink.
564 cmSystemTools::RemoveFile(toFile);
566 // Create destination directory if it doesn't exist
567 cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
569 // Create the symlink.
570 if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
571 std::ostringstream e;
572 e << this->Name << " cannot duplicate symlink \"" << fromFile
573 << "\" at \"" << toFile
574 << "\": " << cmSystemTools::GetLastSystemError() << ".";
575 this->Status.SetError(e.str());
583 bool cmFileCopier::InstallFile(const std::string& fromFile,
584 const std::string& toFile,
585 MatchProperties match_properties)
587 // Determine whether we will copy the file.
590 // If both files exist with the same time do not copy.
591 if (!this->FileTimes.DifferS(fromFile, toFile)) {
596 // Inform the user about this file installation.
597 this->ReportCopy(toFile, TypeFile, copy);
600 if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
601 std::ostringstream e;
602 e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
603 << toFile << "\": " << cmSystemTools::GetLastSystemError() << ".";
604 this->Status.SetError(e.str());
608 // Set the file modification time of the destination file.
609 if (copy && !this->Always) {
610 // Add write permission so we can set the file time.
611 // Permissions are set unconditionally below anyway.
613 if (cmSystemTools::GetPermissions(toFile, perm)) {
614 cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
616 if (!cmFileTimes::Copy(fromFile, toFile)) {
617 std::ostringstream e;
618 e << this->Name << " cannot set modification time on \"" << toFile
619 << "\": " << cmSystemTools::GetLastSystemError() << ".";
620 this->Status.SetError(e.str());
625 // Set permissions of the destination file.
627 (match_properties.Permissions ? match_properties.Permissions
628 : this->FilePermissions);
630 // No permissions were explicitly provided but the user requested
631 // that the source file permissions be used.
632 cmSystemTools::GetPermissions(fromFile, permissions);
634 return this->SetPermissions(toFile, permissions);
637 bool cmFileCopier::InstallDirectory(const std::string& source,
638 const std::string& destination,
639 MatchProperties match_properties)
641 // Inform the user about this directory installation.
642 this->ReportCopy(destination, TypeDir,
643 !cmSystemTools::FileIsDirectory(destination));
645 // check if default dir creation permissions were set
646 mode_t default_dir_mode_v = 0;
647 mode_t* default_dir_mode = &default_dir_mode_v;
648 if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
652 // Make sure the destination directory exists.
653 if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
654 std::ostringstream e;
655 e << this->Name << " cannot make directory \"" << destination
656 << "\": " << cmSystemTools::GetLastSystemError() << ".";
657 this->Status.SetError(e.str());
661 // Compute the requested permissions for the destination directory.
663 (match_properties.Permissions ? match_properties.Permissions
664 : this->DirPermissions);
666 // No permissions were explicitly provided but the user requested
667 // that the source directory permissions be used.
668 cmSystemTools::GetPermissions(source, permissions);
671 // Compute the set of permissions required on this directory to
672 // recursively install files and subdirectories safely.
673 mode_t required_permissions =
674 mode_owner_read | mode_owner_write | mode_owner_execute;
676 // If the required permissions are specified it is safe to set the
677 // final permissions now. Otherwise we must add the required
678 // permissions temporarily during file installation.
679 mode_t permissions_before = 0;
680 mode_t permissions_after = 0;
681 if ((permissions & required_permissions) == required_permissions) {
682 permissions_before = permissions;
684 permissions_before = permissions | required_permissions;
685 permissions_after = permissions;
688 // Set the required permissions of the destination directory.
689 if (!this->SetPermissions(destination, permissions_before)) {
693 // Load the directory contents to traverse it recursively.
694 cmsys::Directory dir;
695 if (!source.empty()) {
698 unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
699 for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
700 if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
701 strcmp(dir.GetFile(fileNum), "..") == 0)) {
702 std::string fromPath = cmStrCat(source, '/', dir.GetFile(fileNum));
703 std::string toPath = cmStrCat(destination, '/', dir.GetFile(fileNum));
704 if (!this->Install(fromPath, toPath)) {
710 // Set the requested permissions of the destination directory.
711 return this->SetPermissions(destination, permissions_after);