Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFileCopier.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst 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 "cmList.h"
13
#include "cmMakefile.h"
14
#include "cmStringAlgorithms.h"
15
#include "cmSystemTools.h"
16
#include "cmValue.h"
17
18
#ifdef _WIN32
19
#  include <winerror.h>
20
21
#  include "cmsys/FStream.hxx"
22
#else
23
#  include <cerrno>
24
#endif
25
26
#include <utility>
27
28
using namespace cmFSPermissions;
29
30
cmFileCopier::cmFileCopier(cmExecutionStatus& status, char const* name)
31
0
  : Status(status)
32
0
  , Makefile(&status.GetMakefile())
33
0
  , Name(name)
34
0
{
35
0
}
36
37
0
cmFileCopier::~cmFileCopier() = default;
38
39
cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
40
  std::string const& file)
41
0
{
42
  // Match rules are case-insensitive on some platforms.
43
#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
44
  std::string const file_to_match = cmSystemTools::LowerCase(file);
45
#else
46
0
  std::string const& file_to_match = file;
47
0
#endif
48
49
  // Collect properties from all matching rules.
50
0
  bool matched = false;
51
0
  MatchProperties result;
52
0
  for (MatchRule& mr : this->MatchRules) {
53
0
    if (mr.Regex.find(file_to_match)) {
54
0
      matched = true;
55
0
      result.Exclude |= mr.Properties.Exclude;
56
0
      result.Permissions |= mr.Properties.Permissions;
57
0
    }
58
0
  }
59
0
  if (!matched && !this->MatchlessFiles) {
60
0
    result.Exclude = !cmSystemTools::FileIsDirectory(file);
61
0
  }
62
0
  return result;
63
0
}
64
65
bool cmFileCopier::SetPermissions(std::string const& toFile,
66
                                  mode_t permissions)
67
0
{
68
0
  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
0
    auto perm_status = cmSystemTools::SetPermissions(toFile, permissions);
89
0
    if (!perm_status) {
90
0
      this->Status.SetError(cmStrCat(this->Name,
91
0
                                     " cannot set permissions on \"", toFile,
92
0
                                     "\": ", perm_status.GetString(), '.'));
93
0
      return false;
94
0
    }
95
0
  }
96
0
  return true;
97
0
}
98
99
// Translate an argument to a permissions bit.
100
bool cmFileCopier::CheckPermissions(std::string const& arg,
101
                                    mode_t& permissions)
102
0
{
103
0
  if (!cmFSPermissions::stringToModeT(arg, permissions)) {
104
0
    this->Status.SetError(
105
0
      cmStrCat(this->Name, " given invalid permission \"", arg, "\"."));
106
0
    return false;
107
0
  }
108
0
  return true;
109
0
}
110
111
std::string const& cmFileCopier::ToName(std::string const& fromName)
112
0
{
113
0
  return fromName;
114
0
}
115
116
bool cmFileCopier::ReportMissing(std::string const& fromFile)
117
0
{
118
  // The input file does not exist and installation is not optional.
119
0
  this->Status.SetError(cmStrCat(this->Name, " cannot find \"", fromFile,
120
0
                                 "\": ", cmSystemTools::GetLastSystemError(),
121
0
                                 '.'));
122
0
  return false;
123
0
}
124
125
void cmFileCopier::NotBeforeMatch(std::string const& arg)
126
0
{
127
0
  this->Status.SetError(
128
0
    cmStrCat("option ", arg, " may not appear before PATTERN or REGEX."));
129
0
  this->Doing = DoingError;
130
0
}
131
132
void cmFileCopier::NotAfterMatch(std::string const& arg)
133
0
{
134
0
  this->Status.SetError(
135
0
    cmStrCat("option ", arg, " may not appear after PATTERN or REGEX."));
136
0
  this->Doing = DoingError;
137
0
}
138
139
void cmFileCopier::DefaultFilePermissions()
140
0
{
141
  // Use read/write permissions.
142
0
  this->FilePermissions = 0;
143
0
  this->FilePermissions |= mode_owner_read;
144
0
  this->FilePermissions |= mode_owner_write;
145
0
  this->FilePermissions |= mode_group_read;
146
0
  this->FilePermissions |= mode_world_read;
147
0
}
148
149
void cmFileCopier::DefaultDirectoryPermissions()
150
0
{
151
  // Use read/write/executable permissions.
152
0
  this->DirPermissions = 0;
153
0
  this->DirPermissions |= mode_owner_read;
154
0
  this->DirPermissions |= mode_owner_write;
155
0
  this->DirPermissions |= mode_owner_execute;
156
0
  this->DirPermissions |= mode_group_read;
157
0
  this->DirPermissions |= mode_group_execute;
158
0
  this->DirPermissions |= mode_world_read;
159
0
  this->DirPermissions |= mode_world_execute;
160
0
}
161
162
bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
163
0
{
164
  // check if default dir creation permissions were set
165
0
  cmValue default_dir_install_permissions = this->Makefile->GetDefinition(
166
0
    "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
167
0
  if (cmNonempty(default_dir_install_permissions)) {
168
0
    cmList items{ *default_dir_install_permissions };
169
0
    for (auto const& arg : items) {
170
0
      if (!this->CheckPermissions(arg, **mode)) {
171
0
        this->Status.SetError(
172
0
          " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable.");
173
0
        return false;
174
0
      }
175
0
    }
176
0
  } else {
177
0
    *mode = nullptr;
178
0
  }
179
180
0
  return true;
181
0
}
182
183
bool cmFileCopier::Parse(std::vector<std::string> const& args)
184
0
{
185
0
  this->Doing = DoingFiles;
186
0
  for (unsigned int i = 1; i < args.size(); ++i) {
187
    // Check this argument.
188
0
    if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
189
0
      this->Status.SetError(
190
0
        cmStrCat("called with unknown argument \"", args[i], "\"."));
191
0
      return false;
192
0
    }
193
194
    // Quit if an argument is invalid.
195
0
    if (this->Doing == DoingError) {
196
0
      return false;
197
0
    }
198
0
  }
199
200
  // Require a destination.
201
0
  if (this->Destination.empty()) {
202
0
    this->Status.SetError(cmStrCat(this->Name, " given no DESTINATION"));
203
0
    return false;
204
0
  }
205
206
  // If file permissions were not specified set default permissions.
207
0
  if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
208
0
    this->DefaultFilePermissions();
209
0
  }
210
211
  // If directory permissions were not specified set default permissions.
212
0
  if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
213
0
    this->DefaultDirectoryPermissions();
214
0
  }
215
216
0
  return true;
217
0
}
218
219
bool cmFileCopier::CheckKeyword(std::string const& arg)
220
0
{
221
0
  if (arg == "DESTINATION") {
222
0
    if (this->CurrentMatchRule) {
223
0
      this->NotAfterMatch(arg);
224
0
    } else {
225
0
      this->Doing = DoingDestination;
226
0
    }
227
0
  } else if (arg == "FILES_FROM_DIR") {
228
0
    if (this->CurrentMatchRule) {
229
0
      this->NotAfterMatch(arg);
230
0
    } else {
231
0
      this->Doing = DoingFilesFromDir;
232
0
    }
233
0
  } else if (arg == "PATTERN") {
234
0
    this->Doing = DoingPattern;
235
0
  } else if (arg == "REGEX") {
236
0
    this->Doing = DoingRegex;
237
0
  } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
238
0
    this->FollowSymlinkChain = true;
239
0
    this->Doing = DoingNone;
240
0
  } else if (arg == "EXCLUDE") {
241
    // Add this property to the current match rule.
242
0
    if (this->CurrentMatchRule) {
243
0
      this->CurrentMatchRule->Properties.Exclude = true;
244
0
      this->Doing = DoingNone;
245
0
    } else {
246
0
      this->NotBeforeMatch(arg);
247
0
    }
248
0
  } else if (arg == "PERMISSIONS") {
249
0
    if (this->CurrentMatchRule) {
250
0
      this->Doing = DoingPermissionsMatch;
251
0
    } else {
252
0
      this->NotBeforeMatch(arg);
253
0
    }
254
0
  } else if (arg == "FILE_PERMISSIONS") {
255
0
    if (this->CurrentMatchRule) {
256
0
      this->NotAfterMatch(arg);
257
0
    } else {
258
0
      this->Doing = DoingPermissionsFile;
259
0
      this->UseGivenPermissionsFile = true;
260
0
    }
261
0
  } else if (arg == "DIRECTORY_PERMISSIONS") {
262
0
    if (this->CurrentMatchRule) {
263
0
      this->NotAfterMatch(arg);
264
0
    } else {
265
0
      this->Doing = DoingPermissionsDir;
266
0
      this->UseGivenPermissionsDir = true;
267
0
    }
268
0
  } else if (arg == "USE_SOURCE_PERMISSIONS") {
269
0
    if (this->CurrentMatchRule) {
270
0
      this->NotAfterMatch(arg);
271
0
    } else {
272
0
      this->Doing = DoingNone;
273
0
      this->UseSourcePermissions = true;
274
0
    }
275
0
  } else if (arg == "NO_SOURCE_PERMISSIONS") {
276
0
    if (this->CurrentMatchRule) {
277
0
      this->NotAfterMatch(arg);
278
0
    } else {
279
0
      this->Doing = DoingNone;
280
0
      this->UseSourcePermissions = false;
281
0
    }
282
0
  } else if (arg == "FILES_MATCHING") {
283
0
    if (this->CurrentMatchRule) {
284
0
      this->NotAfterMatch(arg);
285
0
    } else {
286
0
      this->Doing = DoingNone;
287
0
      this->MatchlessFiles = false;
288
0
    }
289
0
  } else {
290
0
    return false;
291
0
  }
292
0
  return true;
293
0
}
294
295
bool cmFileCopier::CheckValue(std::string const& arg)
296
0
{
297
0
  switch (this->Doing) {
298
0
    case DoingFiles:
299
0
      this->Files.push_back(arg);
300
0
      break;
301
0
    case DoingDestination:
302
0
      if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
303
0
        this->Destination = arg;
304
0
      } else {
305
0
        this->Destination =
306
0
          cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', arg);
307
0
      }
308
0
      this->Doing = DoingNone;
309
0
      break;
310
0
    case DoingFilesFromDir:
311
0
      if (cmSystemTools::FileIsFullPath(arg)) {
312
0
        this->FilesFromDir = arg;
313
0
      } else {
314
0
        this->FilesFromDir =
315
0
          cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', arg);
316
0
      }
317
0
      cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
318
0
      this->Doing = DoingNone;
319
0
      break;
320
0
    case DoingPattern: {
321
      // Convert the pattern to a regular expression.  Require a
322
      // leading slash and trailing end-of-string in the matched
323
      // string to make sure the pattern matches only whole file
324
      // names.
325
0
      std::string regex =
326
0
        cmStrCat('/', cmsys::Glob::PatternToRegex(arg, false), '$');
327
0
      this->MatchRules.emplace_back(regex);
328
0
      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
329
0
      if (this->CurrentMatchRule->Regex.is_valid()) {
330
0
        this->Doing = DoingNone;
331
0
      } else {
332
0
        this->Status.SetError(
333
0
          cmStrCat("could not compile PATTERN \"", arg, "\"."));
334
0
        this->Doing = DoingError;
335
0
      }
336
0
    } break;
337
0
    case DoingRegex:
338
0
      this->MatchRules.emplace_back(arg);
339
0
      this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
340
0
      if (this->CurrentMatchRule->Regex.is_valid()) {
341
0
        this->Doing = DoingNone;
342
0
      } else {
343
0
        this->Status.SetError(
344
0
          cmStrCat("could not compile REGEX \"", arg, "\"."));
345
0
        this->Doing = DoingError;
346
0
      }
347
0
      break;
348
0
    case DoingPermissionsFile:
349
0
      if (!this->CheckPermissions(arg, this->FilePermissions)) {
350
0
        this->Doing = DoingError;
351
0
      }
352
0
      break;
353
0
    case DoingPermissionsDir:
354
0
      if (!this->CheckPermissions(arg, this->DirPermissions)) {
355
0
        this->Doing = DoingError;
356
0
      }
357
0
      break;
358
0
    case DoingPermissionsMatch:
359
0
      if (!this->CheckPermissions(
360
0
            arg, this->CurrentMatchRule->Properties.Permissions)) {
361
0
        this->Doing = DoingError;
362
0
      }
363
0
      break;
364
0
    default:
365
0
      return false;
366
0
  }
367
0
  return true;
368
0
}
369
370
bool cmFileCopier::Run(std::vector<std::string> const& args)
371
0
{
372
0
  if (!this->Parse(args)) {
373
0
    return false;
374
0
  }
375
376
0
  for (std::string const& f : this->Files) {
377
0
    std::string file;
378
0
    if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
379
0
      if (!this->FilesFromDir.empty()) {
380
0
        file = cmStrCat(this->FilesFromDir, '/', f);
381
0
      } else {
382
0
        file = cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', f);
383
0
      }
384
0
    } else if (!this->FilesFromDir.empty()) {
385
0
      this->Status.SetError("option FILES_FROM_DIR requires all files "
386
0
                            "to be specified as relative paths.");
387
0
      return false;
388
0
    } else {
389
0
      file = f;
390
0
    }
391
392
    // Split the input file into its directory and name components.
393
0
    std::vector<std::string> fromPathComponents;
394
0
    cmSystemTools::SplitPath(file, fromPathComponents);
395
0
    std::string fromName = *(fromPathComponents.end() - 1);
396
0
    std::string fromDir = cmSystemTools::JoinPath(
397
0
      fromPathComponents.begin(), fromPathComponents.end() - 1);
398
399
    // Compute the full path to the destination file.
400
0
    std::string toFile = this->Destination;
401
0
    if (!this->FilesFromDir.empty()) {
402
0
      std::string dir = cmSystemTools::GetFilenamePath(f);
403
0
      if (!dir.empty()) {
404
0
        toFile = cmStrCat(std::move(toFile), '/', dir);
405
0
      }
406
0
    }
407
0
    std::string const& toName = this->ToName(fromName);
408
0
    if (!toName.empty()) {
409
0
      toFile = cmStrCat(std::move(toFile), '/', toName);
410
0
    }
411
412
    // Construct the full path to the source file.  The file name may
413
    // have been changed above.
414
0
    std::string fromFile = fromDir;
415
0
    if (!fromName.empty()) {
416
0
      fromFile = cmStrCat(std::move(fromFile), '/', std::move(fromName));
417
0
    }
418
419
0
    if (!this->Install(fromFile, toFile)) {
420
0
      return false;
421
0
    }
422
0
  }
423
0
  return true;
424
0
}
425
426
bool cmFileCopier::Install(std::string const& fromFile,
427
                           std::string const& toFile)
428
0
{
429
0
  if (fromFile.empty()) {
430
0
    this->Status.SetError(
431
0
      "INSTALL encountered an empty string input file name.");
432
0
    return false;
433
0
  }
434
435
  // Collect any properties matching this file name.
436
0
  MatchProperties match_properties = this->CollectMatchProperties(fromFile);
437
438
  // Skip the file if it is excluded.
439
0
  if (match_properties.Exclude) {
440
0
    return true;
441
0
  }
442
443
0
  if (cmSystemTools::SameFile(fromFile, toFile)) {
444
0
    return true;
445
0
  }
446
447
0
  std::string newFromFile = fromFile;
448
0
  std::string newToFile = toFile;
449
450
0
  if (this->FollowSymlinkChain &&
451
0
      !this->InstallSymlinkChain(newFromFile, newToFile)) {
452
0
    return false;
453
0
  }
454
455
0
  if (cmSystemTools::FileIsSymlink(newFromFile)) {
456
0
    return this->InstallSymlink(newFromFile, newToFile);
457
0
  }
458
0
  if (cmSystemTools::FileIsDirectory(newFromFile)) {
459
0
    return this->InstallDirectory(newFromFile, newToFile, match_properties);
460
0
  }
461
0
  if (cmSystemTools::FileExists(newFromFile)) {
462
0
    return this->InstallFile(newFromFile, newToFile, match_properties);
463
0
  }
464
0
  return this->ReportMissing(newFromFile);
465
0
}
466
467
bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
468
                                       std::string& toFile)
469
0
{
470
0
  std::string newFromFile;
471
0
  std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
472
0
  while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
473
0
    if (!cmSystemTools::FileIsFullPath(newFromFile)) {
474
0
      std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
475
0
      newFromFile = cmStrCat(fromFilePath, '/', std::move(newFromFile));
476
0
    }
477
478
0
    std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
479
480
0
    bool copy = true;
481
0
    if (!this->Always) {
482
0
      std::string oldSymlinkTarget;
483
0
      if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
484
0
        if (symlinkTarget == oldSymlinkTarget) {
485
0
          copy = false;
486
0
        }
487
0
      }
488
0
    }
489
490
0
    this->ReportCopy(toFile, TypeLink, copy);
491
492
0
    if (copy) {
493
0
      cmSystemTools::RemoveFile(toFile);
494
0
      cmSystemTools::MakeDirectory(toFilePath);
495
496
0
      cmsys::Status status =
497
0
        cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
498
0
      if (!status) {
499
0
        std::string e = cmStrCat(this->Name, " cannot create symlink\n  ",
500
0
                                 toFile, "\nbecause: ", status.GetString());
501
0
        this->Status.SetError(e);
502
0
        return false;
503
0
      }
504
0
    }
505
506
0
    fromFile = newFromFile;
507
0
    toFile = cmStrCat(toFilePath, '/', symlinkTarget);
508
0
  }
509
510
0
  return true;
511
0
}
512
513
bool cmFileCopier::InstallSymlink(std::string const& fromFile,
514
                                  std::string const& toFile)
515
0
{
516
  // Read the original symlink.
517
0
  std::string symlinkTarget;
518
0
  auto read_symlink_status =
519
0
    cmSystemTools::ReadSymlink(fromFile, symlinkTarget);
520
0
  if (!read_symlink_status) {
521
0
    this->Status.SetError(cmStrCat(
522
0
      this->Name, " cannot read symlink \"", fromFile, "\" to duplicate at \"",
523
0
      toFile, "\": ", read_symlink_status.GetString(), '.'));
524
0
    return false;
525
0
  }
526
527
  // Compare the symlink value to that at the destination if not
528
  // always installing.
529
0
  bool copy = true;
530
0
  if (!this->Always) {
531
0
    std::string oldSymlinkTarget;
532
0
    if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
533
0
      if (symlinkTarget == oldSymlinkTarget) {
534
0
        copy = false;
535
0
      }
536
0
    }
537
0
  }
538
539
  // Inform the user about this file installation.
540
0
  this->ReportCopy(toFile, TypeLink, copy);
541
542
0
  if (copy) {
543
    // Remove the destination file so we can always create the symlink.
544
0
    cmSystemTools::RemoveFile(toFile);
545
546
    // Create destination directory if it doesn't exist
547
0
    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
548
549
    // Create the symlink.
550
0
    cmsys::Status status =
551
0
      cmSystemTools::CreateSymlinkQuietly(symlinkTarget, toFile);
552
0
    if (!status) {
553
#ifdef _WIN32
554
      bool const errorFileExists = status.GetWindows() == ERROR_FILE_EXISTS;
555
#else
556
0
      bool const errorFileExists = status.GetPOSIX() == EEXIST;
557
0
#endif
558
0
      std::string reason;
559
0
      if (errorFileExists && cmSystemTools::FileIsDirectory(toFile)) {
560
0
        reason = "A directory already exists at that location";
561
0
      } else {
562
0
        reason = status.GetString();
563
0
      }
564
0
      std::string e =
565
0
        cmStrCat(this->Name, " cannot duplicate symlink\n  ", fromFile,
566
0
                 "\nat\n  ", toFile, "\nbecause: ", reason);
567
0
      this->Status.SetError(e);
568
0
      return false;
569
0
    }
570
0
  }
571
572
0
  return true;
573
0
}
574
575
bool cmFileCopier::InstallFile(std::string const& fromFile,
576
                               std::string const& toFile,
577
                               MatchProperties match_properties)
578
0
{
579
  // Determine whether we will copy the file.
580
0
  bool copy = true;
581
0
  if (!this->Always) {
582
    // If both files exist with the same time do not copy.
583
0
    if (!this->FileTimes.DifferS(fromFile, toFile)) {
584
0
      copy = false;
585
0
    }
586
0
  }
587
588
  // Inform the user about this file installation.
589
0
  this->ReportCopy(toFile, TypeFile, copy);
590
591
  // Copy the file.
592
0
  if (copy) {
593
0
    auto copy_status = cmSystemTools::CopyAFile(fromFile, toFile);
594
0
    if (!copy_status) {
595
0
      this->Status.SetError(cmStrCat(this->Name, " cannot copy file \"",
596
0
                                     fromFile, "\" to \"", toFile,
597
0
                                     "\": ", copy_status.GetString(), '.'));
598
0
      return false;
599
0
    }
600
0
  }
601
602
  // Set the file modification time of the destination file.
603
0
  if (copy && !this->Always) {
604
    // Add write permission so we can set the file time.
605
    // Permissions are set unconditionally below anyway.
606
0
    mode_t perm = 0;
607
0
    if (cmSystemTools::GetPermissions(toFile, perm)) {
608
0
      cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
609
0
    }
610
0
    auto copy_status = cmFileTimes::Copy(fromFile, toFile);
611
0
    if (!copy_status) {
612
0
      this->Status.SetError(
613
0
        cmStrCat(this->Name, " cannot set modification time on \"", toFile,
614
0
                 "\": ", copy_status.GetString(), '.'));
615
0
      return false;
616
0
    }
617
0
  }
618
619
  // Set permissions of the destination file.
620
0
  mode_t permissions =
621
0
    (match_properties.Permissions ? match_properties.Permissions
622
0
                                  : this->FilePermissions);
623
0
  if (!permissions) {
624
    // No permissions were explicitly provided but the user requested
625
    // that the source file permissions be used.
626
0
    cmSystemTools::GetPermissions(fromFile, permissions);
627
0
  }
628
0
  return this->SetPermissions(toFile, permissions);
629
0
}
630
631
bool cmFileCopier::InstallDirectory(std::string const& source,
632
                                    std::string const& destination,
633
                                    MatchProperties match_properties)
634
0
{
635
  // Inform the user about this directory installation.
636
0
  this->ReportCopy(destination, TypeDir,
637
0
                   !( // Report "Up-to-date:" for existing directories,
638
                      // but not symlinks to them.
639
0
                     cmSystemTools::FileIsDirectory(destination) &&
640
0
                     !cmSystemTools::FileIsSymlink(destination)));
641
642
  // check if default dir creation permissions were set
643
0
  mode_t default_dir_mode_v = 0;
644
0
  mode_t* default_dir_mode = &default_dir_mode_v;
645
0
  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
646
0
    return false;
647
0
  }
648
649
  // Make sure the destination directory exists.
650
0
  auto makedir_status =
651
0
    cmSystemTools::MakeDirectory(destination, default_dir_mode);
652
0
  if (!makedir_status) {
653
0
    this->Status.SetError(cmStrCat(this->Name, " cannot make directory \"",
654
0
                                   destination,
655
0
                                   "\": ", makedir_status.GetString(), '.'));
656
0
    return false;
657
0
  }
658
659
  // Compute the requested permissions for the destination directory.
660
0
  mode_t permissions =
661
0
    (match_properties.Permissions ? match_properties.Permissions
662
0
                                  : this->DirPermissions);
663
0
  if (!permissions) {
664
    // No permissions were explicitly provided but the user requested
665
    // that the source directory permissions be used.
666
0
    cmSystemTools::GetPermissions(source, permissions);
667
0
  }
668
669
  // Compute the set of permissions required on this directory to
670
  // recursively install files and subdirectories safely.
671
0
  mode_t required_permissions =
672
0
    mode_owner_read | mode_owner_write | mode_owner_execute;
673
674
  // If the required permissions are specified it is safe to set the
675
  // final permissions now.  Otherwise we must add the required
676
  // permissions temporarily during file installation.
677
0
  mode_t permissions_before = 0;
678
0
  mode_t permissions_after = 0;
679
0
  if ((permissions & required_permissions) == required_permissions) {
680
0
    permissions_before = permissions;
681
0
  } else {
682
0
    permissions_before = permissions | required_permissions;
683
0
    permissions_after = permissions;
684
0
  }
685
686
  // Set the required permissions of the destination directory.
687
0
  if (!this->SetPermissions(destination, permissions_before)) {
688
0
    return false;
689
0
  }
690
691
  // Load the directory contents to traverse it recursively.
692
0
  cmsys::Directory dir;
693
0
  if (!source.empty()) {
694
0
    dir.Load(source);
695
0
  }
696
0
  unsigned long numFiles = dir.GetNumberOfFiles();
697
0
  for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
698
0
    std::string const& file = dir.GetFileName(fileNum);
699
0
    if (file != "." && file != "..") {
700
0
      std::string fromPath = cmStrCat(source, '/', file);
701
0
      std::string toPath = cmStrCat(destination, '/', file);
702
0
      if (!this->Install(fromPath, toPath)) {
703
0
        return false;
704
0
      }
705
0
    }
706
0
  }
707
708
  // Set the requested permissions of the destination directory.
709
0
  return this->SetPermissions(destination, permissions_after);
710
0
}