1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Simple utility to wrap a binary file in a C++ source file.
16
17 #include <algorithm>
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <string>
22 #include <utility>
23 #include <vector>
24
25 #include "absl/strings/ascii.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/str_format.h"
28 #include "absl/strings/str_replace.h"
29 #include "sandboxed_api/util/fileops.h"
30 #include "sandboxed_api/util/raw_logging.h"
31 #include "sandboxed_api/util/strerror.h"
32
33 // C-escapes a character and writes it to a file stream.
FWriteCEscapedC(int c,FILE * out)34 void FWriteCEscapedC(int c, FILE* out) {
35 /* clang-format off */
36 constexpr char kCEscapedLen[256] = {
37 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 4, 4, 2, 4, 4, // \t, \n, \r
38 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
39 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // "
40 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, // '0'..'9'
41 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 'A'..'O'
42 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, // 'P'..'Z', '\'
43 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 'a'..'o'
44 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, // 'p'..'z', DEL
45 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
46 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
47 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
48 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
49 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
50 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
51 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
52 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
53 };
54 /* clang-format on */
55
56 int char_len = kCEscapedLen[c];
57 if (char_len == 1) {
58 fputc(c, out);
59 } else if (char_len == 2) {
60 fputc('\\', out);
61 switch (c) {
62 case '\0':
63 fputc('0', out);
64 break;
65 case '\n':
66 fputc('n', out);
67 break;
68 case '\r':
69 fputc('r', out);
70 break;
71 case '\t':
72 fputc('t', out);
73 break;
74 case '\"':
75 case '\'':
76 case '\\':
77 case '?':
78 fputc(c, out);
79 break;
80 }
81 } else {
82 fputc('\\', out);
83 fputc('0' + c / 64, out);
84 fputc('0' + (c % 64) / 8, out);
85 fputc('0' + c % 8, out);
86 }
87 }
88
89 // Small RAII class that wraps C-style FILE streams and sets up buffering.
90 class File {
91 public:
File(const char * name,const char * mode)92 File(const char* name, const char* mode)
93 : name_{name}, stream_{fopen(name, mode)}, buf_(4096, '\0') {
94 SAPI_RAW_PCHECK(stream_ != nullptr, "Open %s", name_);
95 std::setvbuf(stream_, &buf_[0], _IOFBF, buf_.size());
96 Check();
97 }
~File()98 ~File() { fclose(stream_); }
99
Check()100 void Check() {
101 if (ferror(stream_)) {
102 SAPI_RAW_PLOG(ERROR, "I/O on %s", name_);
103 _Exit(EXIT_FAILURE);
104 }
105 }
106
get() const107 FILE* get() const { return stream_; }
108
109 private:
110 const char* name_;
111 FILE* stream_;
112 std::string buf_;
113 };
114
115 // Format literals for generating the .h file
116 constexpr const char kHFileHeaderFmt[] =
117 R"(// Automatically generated by sapi_cc_embed_data() Bazel rule
118
119 #ifndef SANDBOXED_API_FILE_TOC_H_
120 #define SANDBOXED_API_FILE_TOC_H_
121
122 #include <cstddef>
123
124 struct FileToc {
125 const char* name;
126 const char* data;
127 size_t size;
128 // Not actually used/computed by sapi_cc_embed_data(), this is for
129 // compatibility with legacy code.
130 unsigned char md5digest[16];
131 };
132
133 #endif // SANDBOXED_API_FILE_TOC_H_
134
135 #ifndef %1$s
136 #define %1$s
137
138 )";
139 constexpr const char kHNamespaceBeginFmt[] =
140 R"(namespace %s {
141 )";
142 constexpr const char kHFileTocDefsFmt[] =
143 R"(
144 const FileToc* %1$s_create();
145 size_t %1$s_size();
146 )";
147 constexpr const char kHNamespaceEndFmt[] =
148 R"(
149 } // namespace %s
150 )";
151 constexpr const char kHFileFooterFmt[] =
152 R"(
153 #endif // %s
154 )";
155
156 // Format literals for generating the .cc file out of the input files.
157 constexpr const char kCcFileHeaderFmt[] =
158 R"(// Automatically generated by sapi_cc_embed_data() build rule
159
160 #include "%s.h"
161 #include "absl/base/macros.h"
162 #include "absl/strings/string_view.h"
163
164 )";
165 constexpr const char kCcNamespaceBeginFmt[] =
166 R"(namespace %s {
167
168 )";
169 constexpr const char kCcDataBeginFmt[] =
170 R"(constexpr absl::string_view %s = {")";
171 constexpr const char kCcDataEndFmt[] =
172 R"(", %d};
173 )";
174 constexpr const char kCcFileTocDefsBegin[] =
175 R"(
176 constexpr FileToc kToc[] = {
177 )";
178 constexpr const char kCcFileTocDefsEntryFmt[] =
179 R"( {"%1$s", %2$s.data(), %2$s.size(), {}},
180 )";
181 constexpr const char kCcFileTocDefsEndFmt[] =
182 R"(
183 // Terminate array
184 {nullptr, nullptr, 0, {}},
185 };
186
187 const FileToc* %1$s_create() {
188 return kToc;
189 }
190
191 size_t %1$s_size() {
192 return ABSL_ARRAYSIZE(kToc) - 1;
193 }
194 )";
195 constexpr const char kCcNamespaceEndFmt[] =
196 R"(
197 } // namespace %s
198 )";
199
main(int argc,char * argv[])200 int main(int argc, char* argv[]) {
201 if (argc < 7) {
202 // We're not aiming for human usability here, as this tool is always run as
203 // part of the build.
204 absl::FPrintF(stderr,
205 "%s PACKAGE NAME NAMESPACE OUTPUT_H OUTPUT_CC INPUT...\n",
206 argv[0]);
207 return EXIT_FAILURE;
208 }
209 char** arg = &argv[1];
210
211 const char* package = *arg++;
212 --argc;
213 const char* name = *arg++;
214 std::string toc_ident = absl::StrReplaceAll(name, {{"-", "_"}});
215 --argc;
216
217 const char* ns = *arg++;
218 const bool have_ns = strlen(ns) > 0;
219 --argc;
220
221 { // Write header file first.
222 File out_h(*arg++, "wb");
223 --argc;
224 std::string header_guard = absl::StrFormat("%s_%s_H_", package, toc_ident);
225 std::replace_if(
226 header_guard.begin(), header_guard.end(),
227 [](char c) { return !absl::ascii_isalnum(c); }, '_');
228 absl::FPrintF(out_h.get(), kHFileHeaderFmt, header_guard);
229 if (have_ns) {
230 absl::FPrintF(out_h.get(), kHNamespaceBeginFmt, ns);
231 }
232 absl::FPrintF(out_h.get(), kHFileTocDefsFmt, toc_ident);
233 if (have_ns) {
234 absl::FPrintF(out_h.get(), kHNamespaceEndFmt, ns);
235 }
236 absl::FPrintF(out_h.get(), kHFileFooterFmt, header_guard);
237 out_h.Check();
238 }
239
240 // Write actual translation unit with the data.
241 File out_cc(*arg++, "wb");
242 --argc;
243
244 std::string package_name = package;
245 if (!package_name.empty()) {
246 absl::StrAppend(&package_name, "/");
247 }
248 absl::StrAppend(&package_name, name);
249 absl::FPrintF(out_cc.get(), kCcFileHeaderFmt, package_name);
250 if (have_ns) {
251 absl::FPrintF(out_cc.get(), kCcNamespaceBeginFmt, ns);
252 }
253
254 std::vector<std::pair<std::string, std::string>> toc_entries;
255 while (argc > 1) {
256 const char* in_filename = *arg++;
257 --argc;
258 File in(in_filename, "rb");
259
260 std::string basename = sapi::file_util::fileops::Basename(in_filename);
261 std::string ident = absl::StrCat("k", basename);
262 std::replace_if(
263 ident.begin(), ident.end(),
264 [](char c) { return !absl::ascii_isalnum(c); }, '_');
265 absl::FPrintF(out_cc.get(), kCcDataBeginFmt, ident);
266 // Remember identifiers, they are needed in the kToc array.
267 toc_entries.emplace_back(std::move(basename), std::move(ident));
268
269 int c;
270 while ((c = fgetc(in.get())) != EOF) {
271 FWriteCEscapedC(c, out_cc.get());
272 }
273 in.Check();
274
275 absl::FPrintF(out_cc.get(), kCcDataEndFmt, ftell(in.get()));
276 }
277 absl::FPrintF(out_cc.get(), kCcFileTocDefsBegin);
278 for (const auto& entry : toc_entries) {
279 absl::FPrintF(out_cc.get(), kCcFileTocDefsEntryFmt, entry.first,
280 entry.second);
281 }
282 absl::FPrintF(out_cc.get(), kCcFileTocDefsEndFmt, toc_ident);
283
284 if (have_ns) {
285 absl::FPrintF(out_cc.get(), kCcNamespaceEndFmt, ns);
286 }
287
288 out_cc.Check();
289 return EXIT_SUCCESS;
290 }
291