// Copyright 2017, 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using digest::Digest; using digest::MerkleTree; struct FileEntry { std::string filename; char digest[Digest::kLength * 2 + 1]{}; }; void usage(char** argv) { fprintf(stderr, "Usage: %s [-o OUTPUT | -m MANIFEST] FILE...\n", argv[0]); fprintf(stderr, "\n\ With -o, OUTPUT gets the same format normally written to stdout: HASH - FILE.\n\ With -m, MANIFEST gets \"manifest file\" format: HASH=FILE.\n\ Any argument may be \"@RSPFILE\" to be replaced with the contents of RSPFILE.\n\ "); exit(1); } int handle_argument(char** argv, const char* arg, std::vector* entries) { if (arg[0] == '@') { FILE* rspfile = fopen(&arg[1], "r"); if (!rspfile) { perror(&arg[1]); return 1; } while (!feof(rspfile) && !ferror(rspfile)) { // 2018 macOS hasn't caught up with C99 yet, so can't use %ms here. char filename[4096]; if (fscanf(rspfile, " %4095s", filename) == 1) { handle_argument(argv, filename, entries); } } int result = ferror(rspfile); if (result) { perror(&arg[1]); } fclose(rspfile); return result; } else { entries->push_back({arg}); return 0; } } void handle_entry(FileEntry* entry) { fbl::unique_fd fd{open(entry->filename.c_str(), O_RDONLY)}; if (!fd){ perror(entry->filename.c_str()); exit(1); } struct stat info; if (fstat(fd.get(), &info) < 0) { perror("fstat"); exit(1); } if (!S_ISREG(info.st_mode)) { return; } // Buffer one intermediate node's worth at a time. fbl::unique_ptr tree; Digest digest; size_t len = MerkleTree::GetTreeLength(info.st_size); fbl::AllocChecker ac; tree.reset(new (&ac) uint8_t[len]); if (!ac.check()) { perror("cannot allocate"); exit(1); } void* data = nullptr; if (info.st_size != 0) { data = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd.get(), 0); } if (info.st_size != 0 && data == MAP_FAILED) { perror("mmap"); exit(1); } zx_status_t rc = MerkleTree::Create(data, info.st_size, tree.get(), len, &digest); if (info.st_size != 0 && munmap(data, info.st_size) != 0) { perror("munmap"); exit(1); } if (rc != ZX_OK) { fprintf(stderr, "%s: Merkle tree creation failed: %d\n", entry->filename.c_str(), rc); exit(1); } rc = digest.ToString(entry->digest, sizeof(entry->digest)); if (rc != ZX_OK) { fprintf(stderr, "%s: Unable to print Merkle tree root: %d\n", entry->filename.c_str(), rc); exit(1); } } } // namespace int main(int argc, char** argv) { FILE* outf = stdout; if (argc < 2) { usage(argv); } int argi = 1; bool manifest = !strcmp(argv[1], "-m"); if (manifest || !strcmp(argv[1], "-o")) { if (argc < 4) { usage(argv); } argi = 3; outf = fopen(argv[2], "w"); if (!outf) { perror(argv[2]); return 1; } } std::vector entries; for (; argi < argc; ++argi) { if (handle_argument(argv, argv[argi], &entries)) return 1; } std::vector threads; std::mutex mtx; size_t next_entry = 0; unsigned n_threads = std::thread::hardware_concurrency(); if (!n_threads) { n_threads = 4; } if (n_threads > entries.size()) { n_threads = entries.size(); } for (unsigned i = n_threads; i > 0; --i) { threads.push_back(std::thread([&] { while (true) { unsigned int j; mtx.lock(); j = next_entry++; mtx.unlock(); if (j >= entries.size()) { return; } handle_entry(&entries[j]); } })); } for (unsigned i = 0; i < threads.size(); ++i) { threads[i].join(); } for (const auto& entry : entries) { fprintf(outf, "%s%s%s\n", entry.digest, manifest ? "=" : " - ", entry.filename.c_str()); } return 0; }