cmFileCopier.cxx 23 KB

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