cmFileCopier.cxx 22 KB

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