make_installation_digest.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include <unistd.h>
  5. #include <cstdlib>
  6. #include <string>
  7. #include "common/bazel_working_dir.h"
  8. #include "common/error.h"
  9. #include "common/exe_path.h"
  10. #include "common/filesystem.h"
  11. #include "common/init_llvm.h"
  12. #include "common/map.h"
  13. #include "common/vlog.h"
  14. #include "llvm/ADT/SmallVector.h"
  15. #include "llvm/ADT/StringExtras.h"
  16. #include "llvm/ADT/StringRef.h"
  17. #include "llvm/Support/SHA256.h"
  18. namespace Carbon {
  19. namespace {
  20. // A class implementing our digest program.
  21. //
  22. // The program is started with a call to `Run`, and either returns an error or
  23. // an exit code for `main`. It has a very simple command line interface:
  24. // - An optional flag `--verbose` that must be the first argument if provided.
  25. // - A required positional argument of a manifest file of all the files in a
  26. // Carbon installation that should be added to the digest.
  27. // - A required positional argument of an output file for the digest.
  28. //
  29. // The program reads the manifest of all the files in the Carbon installation,
  30. // and adds each of those files to a running cryptographic digest. Once
  31. // complete, it writes this cryptographic digest to the provided output digest
  32. // file.
  33. //
  34. // The exact digest format is unspecified, but should provide a strong guarantee
  35. // that changes to any of the files in the manifest of the install produce
  36. // different digests.
  37. class DigestProgram {
  38. public:
  39. auto Run(int argc, char** argv) -> ErrorOr<int>;
  40. private:
  41. auto ComputeFileDigest(Filesystem::ReadFileRef file)
  42. -> std::array<uint8_t, 32>;
  43. llvm::raw_ostream* vlog_stream_ = nullptr;
  44. Map<uint64_t, std::array<uint8_t, 32>> file_digests_;
  45. };
  46. auto DigestProgram::Run(int argc, char** argv) -> ErrorOr<int> {
  47. InitLLVM init_llvm(argc, argv);
  48. SetWorkingDirForBazelRun();
  49. llvm::SHA256 sha256;
  50. // If the first argument is `--verbose`, enable verbose logging.
  51. int num_args = 2;
  52. if (argc > 1 && llvm::StringRef(argv[1]) == "--verbose") {
  53. vlog_stream_ = &llvm::errs();
  54. ++num_args;
  55. }
  56. // The last two arguments are required and positional.
  57. if (argc <= num_args) {
  58. return Error(
  59. "Usage: make-installation-digest [--verbose] MANIFEST_FILE "
  60. "OUTPUT_FILE");
  61. }
  62. std::filesystem::path manifest_path = argv[num_args - 1];
  63. std::filesystem::path digest_path = argv[num_args];
  64. CARBON_ASSIGN_OR_RETURN(std::string manifest,
  65. Filesystem::Cwd().ReadFileToString(manifest_path));
  66. llvm::SmallVector<llvm::StringRef> manifest_lines;
  67. llvm::StringRef(manifest).split(manifest_lines, '\n');
  68. // Walk all the install data files in the manifest.
  69. for (llvm::StringRef manifest_line : manifest_lines) {
  70. if (manifest_line.empty()) {
  71. continue;
  72. }
  73. // Compute the full path and installed path for each file. The installed
  74. // path comes from the path components below the `prefix` component.
  75. std::filesystem::path full_path = manifest_line.trim().str();
  76. std::filesystem::path install_path;
  77. bool append = false;
  78. for (const auto& component : full_path) {
  79. if (append) {
  80. install_path /= component;
  81. continue;
  82. }
  83. if (component == "prefix") {
  84. append = true;
  85. }
  86. }
  87. CARBON_VLOG("Digesting file: {0}\n", install_path);
  88. // Add the install path itself to the digest to track the layout of the
  89. // installation data.
  90. sha256.update(install_path.native());
  91. // Open the file and compute its digest to add as well. We use a memoizing
  92. // helper here to avoid re-examining the same file even if there are
  93. // multiple paths to reach that file.
  94. CARBON_ASSIGN_OR_RETURN(
  95. Filesystem::ReadFile file,
  96. Filesystem::Cwd().OpenReadOnly(full_path, Filesystem::OpenExisting));
  97. sha256.update(ComputeFileDigest(file));
  98. }
  99. auto digest = sha256.final();
  100. CARBON_VLOG("Digest: {0}\n", llvm::toHex(digest));
  101. CARBON_RETURN_IF_ERROR(Filesystem::Cwd().WriteFileFromString(
  102. digest_path, llvm::toHex(digest, /*LowerCase=*/true) + "\n"));
  103. return EXIT_SUCCESS;
  104. }
  105. auto DigestProgram::ComputeFileDigest(Filesystem::ReadFileRef file)
  106. -> std::array<uint8_t, 32> {
  107. // Filesystem errors are unlikely here, and the library will check them just
  108. // in case.
  109. Filesystem::FileStatus stat = *file.Stat();
  110. auto result = file_digests_.Insert(stat.unix_inode(), [file]() mutable {
  111. llvm::SHA256 sha256;
  112. // TODO: We could do this more efficiently by using a fixed buffer.
  113. sha256.update(*file.ReadFileToString());
  114. return sha256.final();
  115. });
  116. return result.value();
  117. }
  118. } // namespace
  119. } // namespace Carbon
  120. auto main(int argc, char** argv) -> int {
  121. Carbon::DigestProgram program;
  122. auto result = program.Run(argc, argv);
  123. if (result.ok()) {
  124. return *result;
  125. } else {
  126. llvm::errs() << "error: " << result.error() << "\n";
  127. return EXIT_FAILURE;
  128. }
  129. }