48fc2868296de8dbb1e806de6f7288dbe42ec337
[platform/upstream/cmake.git] / Source / cmFileCopier.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3
4 #include "cmFileCopier.h"
5
6 #include "cmsys/Directory.hxx"
7 #include "cmsys/Glob.hxx"
8
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"
16
17 #ifdef _WIN32
18 #  include "cmsys/FStream.hxx"
19 #endif
20
21 #include <cstring>
22 #include <sstream>
23
24 using namespace cmFSPermissions;
25
26 cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
27   : Status(status)
28   , Makefile(&status.GetMakefile())
29   , Name(name)
30   , Always(false)
31   , MatchlessFiles(true)
32   , FilePermissions(0)
33   , DirPermissions(0)
34   , CurrentMatchRule(nullptr)
35   , UseGivenPermissionsFile(false)
36   , UseGivenPermissionsDir(false)
37   , UseSourcePermissions(true)
38   , FollowSymlinkChain(false)
39   , Doing(DoingNone)
40 {
41 }
42
43 cmFileCopier::~cmFileCopier() = default;
44
45 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
46   const std::string& file)
47 {
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);
51 #else
52   const std::string& file_to_match = file;
53 #endif
54
55   // Collect properties from all matching rules.
56   bool matched = false;
57   MatchProperties result;
58   for (MatchRule& mr : this->MatchRules) {
59     if (mr.Regex.find(file_to_match)) {
60       matched = true;
61       result.Exclude |= mr.Properties.Exclude;
62       result.Permissions |= mr.Properties.Permissions;
63     }
64   }
65   if (!matched && !this->MatchlessFiles) {
66     result.Exclude = !cmSystemTools::FileIsDirectory(file);
67   }
68   return result;
69 }
70
71 bool cmFileCopier::SetPermissions(const std::string& toFile,
72                                   mode_t permissions)
73 {
74   if (permissions) {
75 #ifdef WIN32
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";
79
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);
83       {
84         cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
85         if (permissionStream) {
86           permissionStream << std::oct << permissions << std::endl;
87         }
88         permissionStream.close();
89       }
90       file_time_orig.Store(toFile);
91     }
92 #endif
93
94     if (!cmSystemTools::SetPermissions(toFile, permissions)) {
95       std::ostringstream e;
96       e << this->Name << " cannot set permissions on \"" << toFile
97         << "\": " << cmSystemTools::GetLastSystemError() << ".";
98       this->Status.SetError(e.str());
99       return false;
100     }
101   }
102   return true;
103 }
104
105 // Translate an argument to a permissions bit.
106 bool cmFileCopier::CheckPermissions(std::string const& arg,
107                                     mode_t& permissions)
108 {
109   if (!cmFSPermissions::stringToModeT(arg, permissions)) {
110     std::ostringstream e;
111     e << this->Name << " given invalid permission \"" << arg << "\".";
112     this->Status.SetError(e.str());
113     return false;
114   }
115   return true;
116 }
117
118 std::string const& cmFileCopier::ToName(std::string const& fromName)
119 {
120   return fromName;
121 }
122
123 bool cmFileCopier::ReportMissing(const std::string& fromFile)
124 {
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());
130   return false;
131 }
132
133 void cmFileCopier::NotBeforeMatch(std::string const& arg)
134 {
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;
139 }
140
141 void cmFileCopier::NotAfterMatch(std::string const& arg)
142 {
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;
147 }
148
149 void cmFileCopier::DefaultFilePermissions()
150 {
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;
157 }
158
159 void cmFileCopier::DefaultDirectoryPermissions()
160 {
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;
170 }
171
172 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
173 {
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.");
184         return false;
185       }
186     }
187   } else {
188     *mode = nullptr;
189   }
190
191   return true;
192 }
193
194 bool cmFileCopier::Parse(std::vector<std::string> const& args)
195 {
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());
203       return false;
204     }
205
206     // Quit if an argument is invalid.
207     if (this->Doing == DoingError) {
208       return false;
209     }
210   }
211
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());
217     return false;
218   }
219
220   // If file permissions were not specified set default permissions.
221   if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
222     this->DefaultFilePermissions();
223   }
224
225   // If directory permissions were not specified set default permissions.
226   if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
227     this->DefaultDirectoryPermissions();
228   }
229
230   return true;
231 }
232
233 bool cmFileCopier::CheckKeyword(std::string const& arg)
234 {
235   if (arg == "DESTINATION") {
236     if (this->CurrentMatchRule) {
237       this->NotAfterMatch(arg);
238     } else {
239       this->Doing = DoingDestination;
240     }
241   } else if (arg == "FILES_FROM_DIR") {
242     if (this->CurrentMatchRule) {
243       this->NotAfterMatch(arg);
244     } else {
245       this->Doing = DoingFilesFromDir;
246     }
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;
259     } else {
260       this->NotBeforeMatch(arg);
261     }
262   } else if (arg == "PERMISSIONS") {
263     if (this->CurrentMatchRule) {
264       this->Doing = DoingPermissionsMatch;
265     } else {
266       this->NotBeforeMatch(arg);
267     }
268   } else if (arg == "FILE_PERMISSIONS") {
269     if (this->CurrentMatchRule) {
270       this->NotAfterMatch(arg);
271     } else {
272       this->Doing = DoingPermissionsFile;
273       this->UseGivenPermissionsFile = true;
274     }
275   } else if (arg == "DIRECTORY_PERMISSIONS") {
276     if (this->CurrentMatchRule) {
277       this->NotAfterMatch(arg);
278     } else {
279       this->Doing = DoingPermissionsDir;
280       this->UseGivenPermissionsDir = true;
281     }
282   } else if (arg == "USE_SOURCE_PERMISSIONS") {
283     if (this->CurrentMatchRule) {
284       this->NotAfterMatch(arg);
285     } else {
286       this->Doing = DoingNone;
287       this->UseSourcePermissions = true;
288     }
289   } else if (arg == "NO_SOURCE_PERMISSIONS") {
290     if (this->CurrentMatchRule) {
291       this->NotAfterMatch(arg);
292     } else {
293       this->Doing = DoingNone;
294       this->UseSourcePermissions = false;
295     }
296   } else if (arg == "FILES_MATCHING") {
297     if (this->CurrentMatchRule) {
298       this->NotAfterMatch(arg);
299     } else {
300       this->Doing = DoingNone;
301       this->MatchlessFiles = false;
302     }
303   } else {
304     return false;
305   }
306   return true;
307 }
308
309 bool cmFileCopier::CheckValue(std::string const& arg)
310 {
311   switch (this->Doing) {
312     case DoingFiles:
313       this->Files.push_back(arg);
314       break;
315     case DoingDestination:
316       if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
317         this->Destination = arg;
318       } else {
319         this->Destination =
320           cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
321       }
322       this->Doing = DoingNone;
323       break;
324     case DoingFilesFromDir:
325       if (cmSystemTools::FileIsFullPath(arg)) {
326         this->FilesFromDir = arg;
327       } else {
328         this->FilesFromDir =
329           cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
330       }
331       cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
332       this->Doing = DoingNone;
333       break;
334     case DoingPattern: {
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
338       // names.
339       std::string regex =
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;
345       } else {
346         std::ostringstream e;
347         e << "could not compile PATTERN \"" << arg << "\".";
348         this->Status.SetError(e.str());
349         this->Doing = DoingError;
350       }
351     } break;
352     case DoingRegex:
353       this->MatchRules.emplace_back(arg);
354       this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
355       if (this->CurrentMatchRule->Regex.is_valid()) {
356         this->Doing = DoingNone;
357       } else {
358         std::ostringstream e;
359         e << "could not compile REGEX \"" << arg << "\".";
360         this->Status.SetError(e.str());
361         this->Doing = DoingError;
362       }
363       break;
364     case DoingPermissionsFile:
365       if (!this->CheckPermissions(arg, this->FilePermissions)) {
366         this->Doing = DoingError;
367       }
368       break;
369     case DoingPermissionsDir:
370       if (!this->CheckPermissions(arg, this->DirPermissions)) {
371         this->Doing = DoingError;
372       }
373       break;
374     case DoingPermissionsMatch:
375       if (!this->CheckPermissions(
376             arg, this->CurrentMatchRule->Properties.Permissions)) {
377         this->Doing = DoingError;
378       }
379       break;
380     default:
381       return false;
382   }
383   return true;
384 }
385
386 bool cmFileCopier::Run(std::vector<std::string> const& args)
387 {
388   if (!this->Parse(args)) {
389     return false;
390   }
391
392   for (std::string const& f : this->Files) {
393     std::string file;
394     if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
395       if (!this->FilesFromDir.empty()) {
396         file = this->FilesFromDir;
397       } else {
398         file = this->Makefile->GetCurrentSourceDirectory();
399       }
400       file += "/";
401       file += f;
402     } else if (!this->FilesFromDir.empty()) {
403       this->Status.SetError("option FILES_FROM_DIR requires all files "
404                             "to be specified as relative paths.");
405       return false;
406     } else {
407       file = f;
408     }
409
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);
416
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);
421       if (!dir.empty()) {
422         toFile += "/";
423         toFile += dir;
424       }
425     }
426     std::string const& toName = this->ToName(fromName);
427     if (!toName.empty()) {
428       toFile += "/";
429       toFile += toName;
430     }
431
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()) {
436       fromFile += "/";
437       fromFile += fromName;
438     }
439
440     if (!this->Install(fromFile, toFile)) {
441       return false;
442     }
443   }
444   return true;
445 }
446
447 bool cmFileCopier::Install(const std::string& fromFile,
448                            const std::string& toFile)
449 {
450   if (fromFile.empty()) {
451     this->Status.SetError(
452       "INSTALL encountered an empty string input file name.");
453     return false;
454   }
455
456   // Collect any properties matching this file name.
457   MatchProperties match_properties = this->CollectMatchProperties(fromFile);
458
459   // Skip the file if it is excluded.
460   if (match_properties.Exclude) {
461     return true;
462   }
463
464   if (cmSystemTools::SameFile(fromFile, toFile)) {
465     return true;
466   }
467
468   std::string newFromFile = fromFile;
469   std::string newToFile = toFile;
470
471   if (this->FollowSymlinkChain &&
472       !this->InstallSymlinkChain(newFromFile, newToFile)) {
473     return false;
474   }
475
476   if (cmSystemTools::FileIsSymlink(newFromFile)) {
477     return this->InstallSymlink(newFromFile, newToFile);
478   }
479   if (cmSystemTools::FileIsDirectory(newFromFile)) {
480     return this->InstallDirectory(newFromFile, newToFile, match_properties);
481   }
482   if (cmSystemTools::FileExists(newFromFile)) {
483     return this->InstallFile(newFromFile, newToFile, match_properties);
484   }
485   return this->ReportMissing(newFromFile);
486 }
487
488 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
489                                        std::string& toFile)
490 {
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);
497     }
498
499     std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
500
501     bool copy = true;
502     if (!this->Always) {
503       std::string oldSymlinkTarget;
504       if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
505         if (symlinkTarget == oldSymlinkTarget) {
506           copy = false;
507         }
508       }
509     }
510
511     this->ReportCopy(toFile, TypeLink, copy);
512
513     if (copy) {
514       cmSystemTools::RemoveFile(toFile);
515       cmSystemTools::MakeDirectory(toFilePath);
516
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());
522         return false;
523       }
524     }
525
526     fromFile = newFromFile;
527     toFile = cmStrCat(toFilePath, "/", symlinkTarget);
528   }
529
530   return true;
531 }
532
533 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
534                                   const std::string& toFile)
535 {
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());
544     return false;
545   }
546
547   // Compare the symlink value to that at the destination if not
548   // always installing.
549   bool copy = true;
550   if (!this->Always) {
551     std::string oldSymlinkTarget;
552     if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
553       if (symlinkTarget == oldSymlinkTarget) {
554         copy = false;
555       }
556     }
557   }
558
559   // Inform the user about this file installation.
560   this->ReportCopy(toFile, TypeLink, copy);
561
562   if (copy) {
563     // Remove the destination file so we can always create the symlink.
564     cmSystemTools::RemoveFile(toFile);
565
566     // Create destination directory if it doesn't exist
567     cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
568
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());
576       return false;
577     }
578   }
579
580   return true;
581 }
582
583 bool cmFileCopier::InstallFile(const std::string& fromFile,
584                                const std::string& toFile,
585                                MatchProperties match_properties)
586 {
587   // Determine whether we will copy the file.
588   bool copy = true;
589   if (!this->Always) {
590     // If both files exist with the same time do not copy.
591     if (!this->FileTimes.DifferS(fromFile, toFile)) {
592       copy = false;
593     }
594   }
595
596   // Inform the user about this file installation.
597   this->ReportCopy(toFile, TypeFile, copy);
598
599   // Copy the file.
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());
605     return false;
606   }
607
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.
612     mode_t perm = 0;
613     if (cmSystemTools::GetPermissions(toFile, perm)) {
614       cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
615     }
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());
621       return false;
622     }
623   }
624
625   // Set permissions of the destination file.
626   mode_t permissions =
627     (match_properties.Permissions ? match_properties.Permissions
628                                   : this->FilePermissions);
629   if (!permissions) {
630     // No permissions were explicitly provided but the user requested
631     // that the source file permissions be used.
632     cmSystemTools::GetPermissions(fromFile, permissions);
633   }
634   return this->SetPermissions(toFile, permissions);
635 }
636
637 bool cmFileCopier::InstallDirectory(const std::string& source,
638                                     const std::string& destination,
639                                     MatchProperties match_properties)
640 {
641   // Inform the user about this directory installation.
642   this->ReportCopy(destination, TypeDir,
643                    !cmSystemTools::FileIsDirectory(destination));
644
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)) {
649     return false;
650   }
651
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());
658     return false;
659   }
660
661   // Compute the requested permissions for the destination directory.
662   mode_t permissions =
663     (match_properties.Permissions ? match_properties.Permissions
664                                   : this->DirPermissions);
665   if (!permissions) {
666     // No permissions were explicitly provided but the user requested
667     // that the source directory permissions be used.
668     cmSystemTools::GetPermissions(source, permissions);
669   }
670
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;
675
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;
683   } else {
684     permissions_before = permissions | required_permissions;
685     permissions_after = permissions;
686   }
687
688   // Set the required permissions of the destination directory.
689   if (!this->SetPermissions(destination, permissions_before)) {
690     return false;
691   }
692
693   // Load the directory contents to traverse it recursively.
694   cmsys::Directory dir;
695   if (!source.empty()) {
696     dir.Load(source);
697   }
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)) {
705         return false;
706       }
707     }
708   }
709
710   // Set the requested permissions of the destination directory.
711   return this->SetPermissions(destination, permissions_after);
712 }