1 // Copyright 2017, 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <string>
11 #include <sys/mman.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <thread>
15 #include <unistd.h>
16 #include <vector>
17
18 #include <digest/digest.h>
19 #include <digest/merkle-tree.h>
20 #include <fbl/alloc_checker.h>
21 #include <fbl/unique_fd.h>
22 #include <fbl/unique_ptr.h>
23
24 namespace {
25
26 using digest::Digest;
27 using digest::MerkleTree;
28
29 struct FileEntry {
30 std::string filename;
31 char digest[Digest::kLength * 2 + 1]{};
32 };
33
usage(char ** argv)34 void usage(char** argv) {
35 fprintf(stderr, "Usage: %s [-o OUTPUT | -m MANIFEST] FILE...\n", argv[0]);
36 fprintf(stderr, "\n\
37 With -o, OUTPUT gets the same format normally written to stdout: HASH - FILE.\n\
38 With -m, MANIFEST gets \"manifest file\" format: HASH=FILE.\n\
39 Any argument may be \"@RSPFILE\" to be replaced with the contents of RSPFILE.\n\
40 ");
41 exit(1);
42 }
43
handle_argument(char ** argv,const char * arg,std::vector<FileEntry> * entries)44 int handle_argument(char** argv, const char* arg,
45 std::vector<FileEntry>* entries) {
46 if (arg[0] == '@') {
47 FILE* rspfile = fopen(&arg[1], "r");
48 if (!rspfile) {
49 perror(&arg[1]);
50 return 1;
51 }
52 while (!feof(rspfile) && !ferror(rspfile)) {
53 // 2018 macOS hasn't caught up with C99 yet, so can't use %ms here.
54 char filename[4096];
55 if (fscanf(rspfile, " %4095s", filename) == 1) {
56 handle_argument(argv, filename, entries);
57 }
58 }
59 int result = ferror(rspfile);
60 if (result) {
61 perror(&arg[1]);
62 }
63 fclose(rspfile);
64 return result;
65 } else {
66 entries->push_back({arg});
67 return 0;
68 }
69 }
70
handle_entry(FileEntry * entry)71 void handle_entry(FileEntry* entry) {
72 fbl::unique_fd fd{open(entry->filename.c_str(), O_RDONLY)};
73 if (!fd) {
74 perror(entry->filename.c_str());
75 exit(1);
76 }
77
78 struct stat info;
79 if (fstat(fd.get(), &info) < 0) {
80 perror("fstat");
81 exit(1);
82 }
83 if (!S_ISREG(info.st_mode)) {
84 return;
85 }
86
87 // Buffer one intermediate node's worth at a time.
88 fbl::unique_ptr<uint8_t[]> tree;
89 Digest digest;
90 size_t len = MerkleTree::GetTreeLength(info.st_size);
91 fbl::AllocChecker ac;
92 tree.reset(new (&ac) uint8_t[len]);
93 if (!ac.check()) {
94 perror("cannot allocate");
95 exit(1);
96 }
97 void* data = nullptr;
98 if (info.st_size != 0) {
99 data = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd.get(), 0);
100 }
101 if (info.st_size != 0 && data == MAP_FAILED) {
102 perror("mmap");
103 exit(1);
104 }
105 zx_status_t rc =
106 MerkleTree::Create(data, info.st_size, tree.get(), len, &digest);
107 if (info.st_size != 0 && munmap(data, info.st_size) != 0) {
108 perror("munmap");
109 exit(1);
110 }
111 if (rc != ZX_OK) {
112 fprintf(stderr, "%s: Merkle tree creation failed: %d\n",
113 entry->filename.c_str(), rc);
114 exit(1);
115 }
116 rc = digest.ToString(entry->digest, sizeof(entry->digest));
117 if (rc != ZX_OK) {
118 fprintf(stderr, "%s: Unable to print Merkle tree root: %d\n",
119 entry->filename.c_str(), rc);
120 exit(1);
121 }
122 }
123
124 } // namespace
125
main(int argc,char ** argv)126 int main(int argc, char** argv) {
127 FILE* outf = stdout;
128 if (argc < 2) {
129 usage(argv);
130 }
131
132 int argi = 1;
133 bool manifest = !strcmp(argv[1], "-m");
134 if (manifest || !strcmp(argv[1], "-o")) {
135 if (argc < 4) {
136 usage(argv);
137 }
138 argi = 3;
139 outf = fopen(argv[2], "w");
140 if (!outf) {
141 perror(argv[2]);
142 return 1;
143 }
144 }
145
146 std::vector<FileEntry> entries;
147 for (; argi < argc; ++argi) {
148 if (handle_argument(argv, argv[argi], &entries))
149 return 1;
150 }
151
152 std::vector<std::thread> threads;
153 std::mutex mtx;
154 size_t next_entry = 0;
155 size_t n_threads = std::thread::hardware_concurrency();
156 if (!n_threads) {
157 n_threads = 4;
158 }
159 if (n_threads > entries.size()) {
160 n_threads = entries.size();
161 }
162 for (size_t i = n_threads; i > 0; --i) {
163 threads.push_back(std::thread([&] {
164 while (true) {
165 mtx.lock();
166 auto j = next_entry++;
167 mtx.unlock();
168 if (j >= entries.size()) {
169 return;
170 }
171 handle_entry(&entries[j]);
172 }
173 }));
174 }
175 for (unsigned i = 0; i < threads.size(); ++i) {
176 threads[i].join();
177 }
178
179 for (const auto& entry : entries) {
180 fprintf(outf, "%s%s%s\n",
181 entry.digest, manifest ? "=" : " - ", entry.filename.c_str());
182 }
183
184 return 0;
185 }
186