16678074c3634f696efbb27f4c77bd2985827262
[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 "cmStringAlgorithms.h"
14 #include "cmSystemTools.h"
15 #include "cmValue.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 {
31 }
32
33 cmFileCopier::~cmFileCopier() = default;
34
35 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
36   const std::string& file)
37 {
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);
41 #else
42   const std::string& file_to_match = file;
43 #endif
44
45   // Collect properties from all matching rules.
46   bool matched = false;
47   MatchProperties result;
48   for (MatchRule& mr : this->MatchRules) {
49     if (mr.Regex.find(file_to_match)) {
50       matched = true;
51       result.Exclude |= mr.Properties.Exclude;
52       result.Permissions |= mr.Properties.Permissions;
53     }
54   }
55   if (!matched && !this->MatchlessFiles) {
56     result.Exclude = !cmSystemTools::FileIsDirectory(file);
57   }
58   return result;
59 }
60
61 bool cmFileCopier::SetPermissions(const std::string& toFile,
62                                   mode_t permissions)
63 {
64   if (permissions) {
65 #ifdef _WIN32
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";
69
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);
73       {
74         cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
75         if (permissionStream) {
76           permissionStream << std::oct << permissions << std::endl;
77         }
78         permissionStream.close();
79       }
80       file_time_orig.Store(toFile);
81     }
82 #endif
83
84     if (!cmSystemTools::SetPermissions(toFile, permissions)) {
85       std::ostringstream e;
86       e << this->Name << " cannot set permissions on \"" << toFile
87         << "\": " << cmSystemTools::GetLastSystemError() << ".";
88       this->Status.SetError(e.str());
89       return false;
90     }
91   }
92   return true;
93 }
94
95 // Translate an argument to a permissions bit.
96 bool cmFileCopier::CheckPermissions(std::string const& arg,
97                                     mode_t& permissions)
98 {
99   if (!cmFSPermissions::stringToModeT(arg, permissions)) {
100     std::ostringstream e;
101     e << this->Name << " given invalid permission \"" << arg << "\".";
102     this->Status.SetError(e.str());
103     return false;
104   }
105   return true;
106 }
107
108 std::string const& cmFileCopier::ToName(std::string const& fromName)
109 {
110   return fromName;
111 }
112
113 bool cmFileCopier::ReportMissing(const std::string& fromFile)
114 {
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());
120   return false;
121 }
122
123 void cmFileCopier::NotBeforeMatch(std::string const& arg)
124 {
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;
129 }
130
131 void cmFileCopier::NotAfterMatch(std::string const& arg)
132 {
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;
137 }
138
139 void cmFileCopier::DefaultFilePermissions()
140 {
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;
147 }
148
149 void cmFileCopier::DefaultDirectoryPermissions()
150 {
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;
160 }
161
162 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
163 {
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.");
174         return false;
175       }
176     }
177   } else {
178     *mode = nullptr;
179   }
180
181   return true;
182 }
183
184 bool cmFileCopier::Parse(std::vector<std::string> const& args)
185 {
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());
193       return false;
194     }
195
196     // Quit if an argument is invalid.
197     if (this->Doing == DoingError) {
198       return false;
199     }
200   }
201
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());
207     return false;
208   }
209
210   // If file permissions were not specified set default permissions.
211   if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
212     this->DefaultFilePermissions();
213   }
214
215   // If directory permissions were not specified set default permissions.
216   if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
217     this->DefaultDirectoryPermissions();
218   }
219
220   return true;
221 }
222
223 bool cmFileCopier::CheckKeyword(std::string const& arg)
224 {
225   if (arg == "DESTINATION") {
226     if (this->CurrentMatchRule) {
227       this->NotAfterMatch(arg);
228     } else {
229       this->Doing = DoingDestination;
230     }
231   } else if (arg == "FILES_FROM_DIR") {
232     if (this->CurrentMatchRule) {
233       this->NotAfterMatch(arg);
234     } else {
235       this->Doing = DoingFilesFromDir;
236     }
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;
249     } else {
250       this->NotBeforeMatch(arg);
251     }
252   } else if (arg == "PERMISSIONS") {
253     if (this->CurrentMatchRule) {
254       this->Doing = DoingPermissionsMatch;
255     } else {
256       this->NotBeforeMatch(arg);
257     }
258   } else if (arg == "FILE_PERMISSIONS") {
259     if (this->CurrentMatchRule) {
260       this->NotAfterMatch(arg);
261     } else {
262       this->Doing = DoingPermissionsFile;
263       this->UseGivenPermissionsFile = true;
264     }
265   } else if (arg == "DIRECTORY_PERMISSIONS") {
266     if (this->CurrentMatchRule) {
267       this->NotAfterMatch(arg);
268     } else {
269       this->Doing = DoingPermissionsDir;
270       this->UseGivenPermissionsDir = true;
271     }
272   } else if (arg == "USE_SOURCE_PERMISSIONS") {
273     if (this->CurrentMatchRule) {
274       this->NotAfterMatch(arg);
275     } else {
276       this->Doing = DoingNone;
277       this->UseSourcePermissions = true;
278     }
279   } else if (arg == "NO_SOURCE_PERMISSIONS") {
280     if (this->CurrentMatchRule) {
281       this->NotAfterMatch(arg);
282     } else {
283       this->Doing = DoingNone;
284       this->UseSourcePermissions = false;
285     }
286   } else if (arg == "FILES_MATCHING") {
287     if (this->CurrentMatchRule) {
288       this->NotAfterMatch(arg);
289     } else {
290       this->Doing = DoingNone;
291       this->MatchlessFiles = false;
292     }
293   } else {
294     return false;
295   }
296   return true;
297 }
298
299 bool cmFileCopier::CheckValue(std::string const& arg)
300 {
301   switch (this->Doing) {
302     case DoingFiles:
303       this->Files.push_back(arg);
304       break;
305     case DoingDestination:
306       if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
307         this->Destination = arg;
308       } else {
309         this->Destination =
310           cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
311       }
312       this->Doing = DoingNone;
313       break;
314     case DoingFilesFromDir:
315       if (cmSystemTools::FileIsFullPath(arg)) {
316         this->FilesFromDir = arg;
317       } else {
318         this->FilesFromDir =
319           cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
320       }
321       cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
322       this->Doing = DoingNone;
323       break;
324     case DoingPattern: {
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
328       // names.
329       std::string regex =
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;
335       } else {
336         std::ostringstream e;
337         e << "could not compile PATTERN \"" << arg << "\".";
338         this->Status.SetError(e.str());
339         this->Doing = DoingError;
340       }
341     } break;
342     case DoingRegex:
343       this->MatchRules.emplace_back(arg);
344       this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
345       if (this->CurrentMatchRule->Regex.is_valid()) {
346         this->Doing = DoingNone;
347       } else {
348         std::ostringstream e;
349         e << "could not compile REGEX \"" << arg << "\".";
350         this->Status.SetError(e.str());
351         this->Doing = DoingError;
352       }
353       break;
354     case DoingPermissionsFile:
355       if (!this->CheckPermissions(arg, this->FilePermissions)) {
356         this->Doing = DoingError;
357       }
358       break;
359     case DoingPermissionsDir:
360       if (!this->CheckPermissions(arg, this->DirPermissions)) {
361         this->Doing = DoingError;
362       }
363       break;
364     case DoingPermissionsMatch:
365       if (!this->CheckPermissions(
366             arg, this->CurrentMatchRule->Properties.Permissions)) {
367         this->Doing = DoingError;
368       }
369       break;
370     default:
371       return false;
372   }
373   return true;
374 }
375
376 bool cmFileCopier::Run(std::vector<std::string> const& args)
377 {
378   if (!this->Parse(args)) {
379     return false;
380   }
381
382   for (std::string const& f : this->Files) {
383     std::string file;
384     if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
385       if (!this->FilesFromDir.empty()) {
386         file = this->FilesFromDir;
387       } else {
388         file = this->Makefile->GetCurrentSourceDirectory();
389       }
390       file += "/";
391       file += f;
392     } else if (!this->FilesFromDir.empty()) {
393       this->Status.SetError("option FILES_FROM_DIR requires all files "
394                             "to be specified as relative paths.");
395       return false;
396     } else {
397       file = f;
398     }
399
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);
406
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);
411       if (!dir.empty()) {
412         toFile += "/";
413         toFile += dir;
414       }
415     }
416     std::string const& toName = this->ToName(fromName);
417     if (!toName.empty()) {
418       toFile += "/";
419       toFile += toName;
420     }
421
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()) {
426       fromFile += "/";
427       fromFile += fromName;
428     }
429
430     if (!this->Install(fromFile, toFile)) {
431       return false;
432     }
433   }
434   return true;
435 }
436
437 bool cmFileCopier::Install(const std::string& fromFile,
438                            const std::string& toFile)
439 {
440   if (fromFile.empty()) {
441     this->Status.SetError(
442       "INSTALL encountered an empty string input file name.");
443     return false;
444   }
445
446   // Collect any properties matching this file name.
447   MatchProperties match_properties = this->CollectMatchProperties(fromFile);
448
449   // Skip the file if it is excluded.
450   if (match_properties.Exclude) {
451     return true;
452   }
453
454   if (cmSystemTools::SameFile(fromFile, toFile)) {
455     return true;
456   }
457
458   std::string newFromFile = fromFile;
459   std::string newToFile = toFile;
460
461   if (this->FollowSymlinkChain &&
462       !this->InstallSymlinkChain(newFromFile, newToFile)) {
463     return false;
464   }
465
466   if (cmSystemTools::FileIsSymlink(newFromFile)) {
467     return this->InstallSymlink(newFromFile, newToFile);
468   }
469   if (cmSystemTools::FileIsDirectory(newFromFile)) {
470     return this->InstallDirectory(newFromFile, newToFile, match_properties);
471   }
472   if (cmSystemTools::FileExists(newFromFile)) {
473     return this->InstallFile(newFromFile, newToFile, match_properties);
474   }
475   return this->ReportMissing(newFromFile);
476 }
477
478 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
479                                        std::string& toFile)
480 {
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);
487     }
488
489     std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
490
491     bool copy = true;
492     if (!this->Always) {
493       std::string oldSymlinkTarget;
494       if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
495         if (symlinkTarget == oldSymlinkTarget) {
496           copy = false;
497         }
498       }
499     }
500
501     this->ReportCopy(toFile, TypeLink, copy);
502
503     if (copy) {
504       cmSystemTools::RemoveFile(toFile);
505       cmSystemTools::MakeDirectory(toFilePath);
506
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());
512         return false;
513       }
514     }
515
516     fromFile = newFromFile;
517     toFile = cmStrCat(toFilePath, "/", symlinkTarget);
518   }
519
520   return true;
521 }
522
523 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
524                                   const std::string& toFile)
525 {
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());
534     return false;
535   }
536
537   // Compare the symlink value to that at the destination if not
538   // always installing.
539   bool copy = true;
540   if (!this->Always) {
541     std::string oldSymlinkTarget;
542     if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
543       if (symlinkTarget == oldSymlinkTarget) {
544         copy = false;
545       }
546     }
547   }
548
549   // Inform the user about this file installation.
550   this->ReportCopy(toFile, TypeLink, copy);
551
552   if (copy) {
553     // Remove the destination file so we can always create the symlink.
554     cmSystemTools::RemoveFile(toFile);
555
556     // Create destination directory if it doesn't exist
557     cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
558
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());
566       return false;
567     }
568   }
569
570   return true;
571 }
572
573 bool cmFileCopier::InstallFile(const std::string& fromFile,
574                                const std::string& toFile,
575                                MatchProperties match_properties)
576 {
577   // Determine whether we will copy the file.
578   bool copy = true;
579   if (!this->Always) {
580     // If both files exist with the same time do not copy.
581     if (!this->FileTimes.DifferS(fromFile, toFile)) {
582       copy = false;
583     }
584   }
585
586   // Inform the user about this file installation.
587   this->ReportCopy(toFile, TypeFile, copy);
588
589   // Copy the file.
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());
595     return false;
596   }
597
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.
602     mode_t perm = 0;
603     if (cmSystemTools::GetPermissions(toFile, perm)) {
604       cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
605     }
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());
611       return false;
612     }
613   }
614
615   // Set permissions of the destination file.
616   mode_t permissions =
617     (match_properties.Permissions ? match_properties.Permissions
618                                   : this->FilePermissions);
619   if (!permissions) {
620     // No permissions were explicitly provided but the user requested
621     // that the source file permissions be used.
622     cmSystemTools::GetPermissions(fromFile, permissions);
623   }
624   return this->SetPermissions(toFile, permissions);
625 }
626
627 bool cmFileCopier::InstallDirectory(const std::string& source,
628                                     const std::string& destination,
629                                     MatchProperties match_properties)
630 {
631   // Inform the user about this directory installation.
632   this->ReportCopy(destination, TypeDir,
633                    !cmSystemTools::FileIsDirectory(destination));
634
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)) {
639     return false;
640   }
641
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());
648     return false;
649   }
650
651   // Compute the requested permissions for the destination directory.
652   mode_t permissions =
653     (match_properties.Permissions ? match_properties.Permissions
654                                   : this->DirPermissions);
655   if (!permissions) {
656     // No permissions were explicitly provided but the user requested
657     // that the source directory permissions be used.
658     cmSystemTools::GetPermissions(source, permissions);
659   }
660
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;
665
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;
673   } else {
674     permissions_before = permissions | required_permissions;
675     permissions_after = permissions;
676   }
677
678   // Set the required permissions of the destination directory.
679   if (!this->SetPermissions(destination, permissions_before)) {
680     return false;
681   }
682
683   // Load the directory contents to traverse it recursively.
684   cmsys::Directory dir;
685   if (!source.empty()) {
686     dir.Load(source);
687   }
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)) {
695         return false;
696       }
697     }
698   }
699
700   // Set the requested permissions of the destination directory.
701   return this->SetPermissions(destination, permissions_after);
702 }