Imported Upstream version 3.25.0
[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 <winerror.h>
19
20 #  include "cmsys/FStream.hxx"
21 #else
22 #  include <cerrno>
23 #endif
24
25 #include <cstring>
26 #include <sstream>
27
28 using namespace cmFSPermissions;
29
30 cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
31   : Status(status)
32   , Makefile(&status.GetMakefile())
33   , Name(name)
34 {
35 }
36
37 cmFileCopier::~cmFileCopier() = default;
38
39 cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
40   const std::string& file)
41 {
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);
45 #else
46   const std::string& file_to_match = file;
47 #endif
48
49   // Collect properties from all matching rules.
50   bool matched = false;
51   MatchProperties result;
52   for (MatchRule& mr : this->MatchRules) {
53     if (mr.Regex.find(file_to_match)) {
54       matched = true;
55       result.Exclude |= mr.Properties.Exclude;
56       result.Permissions |= mr.Properties.Permissions;
57     }
58   }
59   if (!matched && !this->MatchlessFiles) {
60     result.Exclude = !cmSystemTools::FileIsDirectory(file);
61   }
62   return result;
63 }
64
65 bool cmFileCopier::SetPermissions(const std::string& toFile,
66                                   mode_t permissions)
67 {
68   if (permissions) {
69 #ifdef _WIN32
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";
73
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);
77       {
78         cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
79         if (permissionStream) {
80           permissionStream << std::oct << permissions << std::endl;
81         }
82         permissionStream.close();
83       }
84       file_time_orig.Store(toFile);
85     }
86 #endif
87
88     if (!cmSystemTools::SetPermissions(toFile, permissions)) {
89       std::ostringstream e;
90       e << this->Name << " cannot set permissions on \"" << toFile
91         << "\": " << cmSystemTools::GetLastSystemError() << ".";
92       this->Status.SetError(e.str());
93       return false;
94     }
95   }
96   return true;
97 }
98
99 // Translate an argument to a permissions bit.
100 bool cmFileCopier::CheckPermissions(std::string const& arg,
101                                     mode_t& permissions)
102 {
103   if (!cmFSPermissions::stringToModeT(arg, permissions)) {
104     std::ostringstream e;
105     e << this->Name << " given invalid permission \"" << arg << "\".";
106     this->Status.SetError(e.str());
107     return false;
108   }
109   return true;
110 }
111
112 std::string const& cmFileCopier::ToName(std::string const& fromName)
113 {
114   return fromName;
115 }
116
117 bool cmFileCopier::ReportMissing(const std::string& fromFile)
118 {
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());
124   return false;
125 }
126
127 void cmFileCopier::NotBeforeMatch(std::string const& arg)
128 {
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;
133 }
134
135 void cmFileCopier::NotAfterMatch(std::string const& arg)
136 {
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;
141 }
142
143 void cmFileCopier::DefaultFilePermissions()
144 {
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;
151 }
152
153 void cmFileCopier::DefaultDirectoryPermissions()
154 {
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;
164 }
165
166 bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
167 {
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.");
178         return false;
179       }
180     }
181   } else {
182     *mode = nullptr;
183   }
184
185   return true;
186 }
187
188 bool cmFileCopier::Parse(std::vector<std::string> const& args)
189 {
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());
197       return false;
198     }
199
200     // Quit if an argument is invalid.
201     if (this->Doing == DoingError) {
202       return false;
203     }
204   }
205
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());
211     return false;
212   }
213
214   // If file permissions were not specified set default permissions.
215   if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
216     this->DefaultFilePermissions();
217   }
218
219   // If directory permissions were not specified set default permissions.
220   if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
221     this->DefaultDirectoryPermissions();
222   }
223
224   return true;
225 }
226
227 bool cmFileCopier::CheckKeyword(std::string const& arg)
228 {
229   if (arg == "DESTINATION") {
230     if (this->CurrentMatchRule) {
231       this->NotAfterMatch(arg);
232     } else {
233       this->Doing = DoingDestination;
234     }
235   } else if (arg == "FILES_FROM_DIR") {
236     if (this->CurrentMatchRule) {
237       this->NotAfterMatch(arg);
238     } else {
239       this->Doing = DoingFilesFromDir;
240     }
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;
253     } else {
254       this->NotBeforeMatch(arg);
255     }
256   } else if (arg == "PERMISSIONS") {
257     if (this->CurrentMatchRule) {
258       this->Doing = DoingPermissionsMatch;
259     } else {
260       this->NotBeforeMatch(arg);
261     }
262   } else if (arg == "FILE_PERMISSIONS") {
263     if (this->CurrentMatchRule) {
264       this->NotAfterMatch(arg);
265     } else {
266       this->Doing = DoingPermissionsFile;
267       this->UseGivenPermissionsFile = true;
268     }
269   } else if (arg == "DIRECTORY_PERMISSIONS") {
270     if (this->CurrentMatchRule) {
271       this->NotAfterMatch(arg);
272     } else {
273       this->Doing = DoingPermissionsDir;
274       this->UseGivenPermissionsDir = true;
275     }
276   } else if (arg == "USE_SOURCE_PERMISSIONS") {
277     if (this->CurrentMatchRule) {
278       this->NotAfterMatch(arg);
279     } else {
280       this->Doing = DoingNone;
281       this->UseSourcePermissions = true;
282     }
283   } else if (arg == "NO_SOURCE_PERMISSIONS") {
284     if (this->CurrentMatchRule) {
285       this->NotAfterMatch(arg);
286     } else {
287       this->Doing = DoingNone;
288       this->UseSourcePermissions = false;
289     }
290   } else if (arg == "FILES_MATCHING") {
291     if (this->CurrentMatchRule) {
292       this->NotAfterMatch(arg);
293     } else {
294       this->Doing = DoingNone;
295       this->MatchlessFiles = false;
296     }
297   } else {
298     return false;
299   }
300   return true;
301 }
302
303 bool cmFileCopier::CheckValue(std::string const& arg)
304 {
305   switch (this->Doing) {
306     case DoingFiles:
307       this->Files.push_back(arg);
308       break;
309     case DoingDestination:
310       if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
311         this->Destination = arg;
312       } else {
313         this->Destination =
314           cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
315       }
316       this->Doing = DoingNone;
317       break;
318     case DoingFilesFromDir:
319       if (cmSystemTools::FileIsFullPath(arg)) {
320         this->FilesFromDir = arg;
321       } else {
322         this->FilesFromDir =
323           cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
324       }
325       cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
326       this->Doing = DoingNone;
327       break;
328     case DoingPattern: {
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
332       // names.
333       std::string regex =
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;
339       } else {
340         std::ostringstream e;
341         e << "could not compile PATTERN \"" << arg << "\".";
342         this->Status.SetError(e.str());
343         this->Doing = DoingError;
344       }
345     } break;
346     case DoingRegex:
347       this->MatchRules.emplace_back(arg);
348       this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
349       if (this->CurrentMatchRule->Regex.is_valid()) {
350         this->Doing = DoingNone;
351       } else {
352         std::ostringstream e;
353         e << "could not compile REGEX \"" << arg << "\".";
354         this->Status.SetError(e.str());
355         this->Doing = DoingError;
356       }
357       break;
358     case DoingPermissionsFile:
359       if (!this->CheckPermissions(arg, this->FilePermissions)) {
360         this->Doing = DoingError;
361       }
362       break;
363     case DoingPermissionsDir:
364       if (!this->CheckPermissions(arg, this->DirPermissions)) {
365         this->Doing = DoingError;
366       }
367       break;
368     case DoingPermissionsMatch:
369       if (!this->CheckPermissions(
370             arg, this->CurrentMatchRule->Properties.Permissions)) {
371         this->Doing = DoingError;
372       }
373       break;
374     default:
375       return false;
376   }
377   return true;
378 }
379
380 bool cmFileCopier::Run(std::vector<std::string> const& args)
381 {
382   if (!this->Parse(args)) {
383     return false;
384   }
385
386   for (std::string const& f : this->Files) {
387     std::string file;
388     if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
389       if (!this->FilesFromDir.empty()) {
390         file = this->FilesFromDir;
391       } else {
392         file = this->Makefile->GetCurrentSourceDirectory();
393       }
394       file += "/";
395       file += f;
396     } else if (!this->FilesFromDir.empty()) {
397       this->Status.SetError("option FILES_FROM_DIR requires all files "
398                             "to be specified as relative paths.");
399       return false;
400     } else {
401       file = f;
402     }
403
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);
410
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);
415       if (!dir.empty()) {
416         toFile += "/";
417         toFile += dir;
418       }
419     }
420     std::string const& toName = this->ToName(fromName);
421     if (!toName.empty()) {
422       toFile += "/";
423       toFile += toName;
424     }
425
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()) {
430       fromFile += "/";
431       fromFile += fromName;
432     }
433
434     if (!this->Install(fromFile, toFile)) {
435       return false;
436     }
437   }
438   return true;
439 }
440
441 bool cmFileCopier::Install(const std::string& fromFile,
442                            const std::string& toFile)
443 {
444   if (fromFile.empty()) {
445     this->Status.SetError(
446       "INSTALL encountered an empty string input file name.");
447     return false;
448   }
449
450   // Collect any properties matching this file name.
451   MatchProperties match_properties = this->CollectMatchProperties(fromFile);
452
453   // Skip the file if it is excluded.
454   if (match_properties.Exclude) {
455     return true;
456   }
457
458   if (cmSystemTools::SameFile(fromFile, toFile)) {
459     return true;
460   }
461
462   std::string newFromFile = fromFile;
463   std::string newToFile = toFile;
464
465   if (this->FollowSymlinkChain &&
466       !this->InstallSymlinkChain(newFromFile, newToFile)) {
467     return false;
468   }
469
470   if (cmSystemTools::FileIsSymlink(newFromFile)) {
471     return this->InstallSymlink(newFromFile, newToFile);
472   }
473   if (cmSystemTools::FileIsDirectory(newFromFile)) {
474     return this->InstallDirectory(newFromFile, newToFile, match_properties);
475   }
476   if (cmSystemTools::FileExists(newFromFile)) {
477     return this->InstallFile(newFromFile, newToFile, match_properties);
478   }
479   return this->ReportMissing(newFromFile);
480 }
481
482 bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
483                                        std::string& toFile)
484 {
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);
491     }
492
493     std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
494
495     bool copy = true;
496     if (!this->Always) {
497       std::string oldSymlinkTarget;
498       if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
499         if (symlinkTarget == oldSymlinkTarget) {
500           copy = false;
501         }
502       }
503     }
504
505     this->ReportCopy(toFile, TypeLink, copy);
506
507     if (copy) {
508       cmSystemTools::RemoveFile(toFile);
509       cmSystemTools::MakeDirectory(toFilePath);
510
511       cmsys::Status status =
512         cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
513       if (!status) {
514         std::string e = cmStrCat(this->Name, " cannot create symlink\n  ",
515                                  toFile, "\nbecause: ", status.GetString());
516         this->Status.SetError(e);
517         return false;
518       }
519     }
520
521     fromFile = newFromFile;
522     toFile = cmStrCat(toFilePath, "/", symlinkTarget);
523   }
524
525   return true;
526 }
527
528 bool cmFileCopier::InstallSymlink(const std::string& fromFile,
529                                   const std::string& toFile)
530 {
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());
539     return false;
540   }
541
542   // Compare the symlink value to that at the destination if not
543   // always installing.
544   bool copy = true;
545   if (!this->Always) {
546     std::string oldSymlinkTarget;
547     if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
548       if (symlinkTarget == oldSymlinkTarget) {
549         copy = false;
550       }
551     }
552   }
553
554   // Inform the user about this file installation.
555   this->ReportCopy(toFile, TypeLink, copy);
556
557   if (copy) {
558     // Remove the destination file so we can always create the symlink.
559     cmSystemTools::RemoveFile(toFile);
560
561     // Create destination directory if it doesn't exist
562     cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
563
564     // Create the symlink.
565     cmsys::Status status =
566       cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
567     if (!status) {
568 #ifdef _WIN32
569       bool const errorFileExists = status.GetWindows() == ERROR_FILE_EXISTS;
570 #else
571       bool const errorFileExists = status.GetPOSIX() == EEXIST;
572 #endif
573       std::string reason;
574       if (errorFileExists && cmSystemTools::FileIsDirectory(toFile)) {
575         reason = "A directory already exists at that location";
576       } else {
577         reason = status.GetString();
578       }
579       std::string e =
580         cmStrCat(this->Name, " cannot duplicate symlink\n  ", fromFile,
581                  "\nat\n  ", toFile, "\nbecause: ", reason);
582       this->Status.SetError(e);
583       return false;
584     }
585   }
586
587   return true;
588 }
589
590 bool cmFileCopier::InstallFile(const std::string& fromFile,
591                                const std::string& toFile,
592                                MatchProperties match_properties)
593 {
594   // Determine whether we will copy the file.
595   bool copy = true;
596   if (!this->Always) {
597     // If both files exist with the same time do not copy.
598     if (!this->FileTimes.DifferS(fromFile, toFile)) {
599       copy = false;
600     }
601   }
602
603   // Inform the user about this file installation.
604   this->ReportCopy(toFile, TypeFile, copy);
605
606   // Copy the file.
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());
612     return false;
613   }
614
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.
619     mode_t perm = 0;
620     if (cmSystemTools::GetPermissions(toFile, perm)) {
621       cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
622     }
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());
628       return false;
629     }
630   }
631
632   // Set permissions of the destination file.
633   mode_t permissions =
634     (match_properties.Permissions ? match_properties.Permissions
635                                   : this->FilePermissions);
636   if (!permissions) {
637     // No permissions were explicitly provided but the user requested
638     // that the source file permissions be used.
639     cmSystemTools::GetPermissions(fromFile, permissions);
640   }
641   return this->SetPermissions(toFile, permissions);
642 }
643
644 bool cmFileCopier::InstallDirectory(const std::string& source,
645                                     const std::string& destination,
646                                     MatchProperties match_properties)
647 {
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)));
654
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)) {
659     return false;
660   }
661
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());
668     return false;
669   }
670
671   // Compute the requested permissions for the destination directory.
672   mode_t permissions =
673     (match_properties.Permissions ? match_properties.Permissions
674                                   : this->DirPermissions);
675   if (!permissions) {
676     // No permissions were explicitly provided but the user requested
677     // that the source directory permissions be used.
678     cmSystemTools::GetPermissions(source, permissions);
679   }
680
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;
685
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;
693   } else {
694     permissions_before = permissions | required_permissions;
695     permissions_after = permissions;
696   }
697
698   // Set the required permissions of the destination directory.
699   if (!this->SetPermissions(destination, permissions_before)) {
700     return false;
701   }
702
703   // Load the directory contents to traverse it recursively.
704   cmsys::Directory dir;
705   if (!source.empty()) {
706     dir.Load(source);
707   }
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)) {
715         return false;
716       }
717     }
718   }
719
720   // Set the requested permissions of the destination directory.
721   return this->SetPermissions(destination, permissions_after);
722 }