blob: bcbe242775e1bc9a3fdf8ac184a0785a7fd5d5e9 [file] [log] [blame]
Damien Martin-Guillerezbf6281d2015-11-19 16:41:33 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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// http://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// ijar.cpp -- .jar -> _interface.jar tool.
16//
17
18#include <stdio.h>
19#include <string.h>
20#include <stdlib.h>
21#include <limits.h>
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000022#include <errno.h>
23#include <memory>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000025#include "third_party/ijar/zip.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000027namespace devtools_ijar {
28
29bool verbose = false;
30
31// Reads a JVM class from classdata_in (of the specified length), and
32// writes out a simplified class to classdata_out, advancing the
Liam Miller-Cushon31c88782016-04-11 22:59:37 +000033// pointer. Returns true if the class should be kept.
34bool StripClass(u1*& classdata_out, const u1* classdata_in, size_t in_length);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000035
36const char* CLASS_EXTENSION = ".class";
37const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
38
cushonf5728452018-03-21 20:30:12 -070039const char *MANIFEST_DIR_PATH = "META-INF/";
40const size_t MANIFEST_DIR_PATH_LENGTH = strlen(MANIFEST_DIR_PATH);
41const char *MANIFEST_PATH = "META-INF/MANIFEST.MF";
42const size_t MANIFEST_PATH_LENGTH = strlen(MANIFEST_PATH);
tomlu8a56b162018-02-09 10:32:40 -080043const char *MANIFEST_HEADER =
cushonf5728452018-03-21 20:30:12 -070044 "Manifest-Version: 1.0\r\n"
45 "Created-By: bazel\r\n";
46const size_t MANIFEST_HEADER_LENGTH = strlen(MANIFEST_HEADER);
tomlu8a56b162018-02-09 10:32:40 -080047// These attributes are used by JavaBuilder, Turbine, and ijar.
48// They must all be kept in sync.
49const char *TARGET_LABEL_KEY = "Target-Label: ";
cushonf5728452018-03-21 20:30:12 -070050const size_t TARGET_LABEL_KEY_LENGTH = strlen(TARGET_LABEL_KEY);
tomlu8a56b162018-02-09 10:32:40 -080051const char *INJECTING_RULE_KIND_KEY = "Injecting-Rule-Kind: ";
cushonf5728452018-03-21 20:30:12 -070052const size_t INJECTING_RULE_KIND_KEY_LENGTH = strlen(INJECTING_RULE_KIND_KEY);
tomlu8a56b162018-02-09 10:32:40 -080053
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000054// ZipExtractorProcessor that select only .class file and use
55// StripClass to generate an interface class, storing as a new file
56// in the specified ZipBuilder.
57class JarStripperProcessor : public ZipExtractorProcessor {
58 public:
59 JarStripperProcessor() {}
60 virtual ~JarStripperProcessor() {}
61
62 virtual void Process(const char* filename, const u4 attr,
63 const u1* data, const size_t size);
64 virtual bool Accept(const char* filename, const u4 attr);
65
66 private:
67 // Not owned by JarStripperProcessor, see SetZipBuilder().
68 ZipBuilder* builder;
69
70 public:
71 // Set the ZipBuilder to add the ijar class to the output zip file.
72 // This pointer should not be deleted while this class is still in use and
73 // it should be set before any call to the Process() method.
74 void SetZipBuilder(ZipBuilder* builder) {
75 this->builder = builder;
76 }
77};
78
79bool JarStripperProcessor::Accept(const char* filename, const u4 attr) {
John Cater0dff43a2017-03-01 16:36:32 +000080 const size_t filename_len = strlen(filename);
cushon6f3af282017-09-15 06:11:42 +020081 if (filename_len < CLASS_EXTENSION_LENGTH ||
82 strcmp(filename + filename_len - CLASS_EXTENSION_LENGTH,
83 CLASS_EXTENSION) != 0) {
84 return false;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000085 }
cushona3bdffe2018-02-05 11:47:14 -080086 return true;
87}
88
89static bool IsModuleInfo(const char* filename) {
cushon6f3af282017-09-15 06:11:42 +020090 const char* slash = strrchr(filename, '/');
91 if (slash == NULL) {
92 slash = filename;
93 } else {
94 slash++;
95 }
cushona3bdffe2018-02-05 11:47:14 -080096 return strcmp(slash, "module-info.class") == 0;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000097}
98
99void JarStripperProcessor::Process(const char* filename, const u4 attr,
100 const u1* data, const size_t size) {
101 if (verbose) {
102 fprintf(stderr, "INFO: StripClass: %s\n", filename);
103 }
cushona3bdffe2018-02-05 11:47:14 -0800104 if (IsModuleInfo(filename)) {
105 u1* q = builder->NewFile(filename, 0);
106 memcpy(q, data, size);
107 builder->FinishFile(size, false, true);
108 } else {
109 u1* buf = reinterpret_cast<u1*>(malloc(size));
110 u1* classdata_out = buf;
111 if (!StripClass(buf, data, size)) {
112 free(classdata_out);
113 return;
114 }
115 u1* q = builder->NewFile(filename, 0);
116 size_t out_length = buf - classdata_out;
117 memcpy(q, classdata_out, out_length);
118 builder->FinishFile(out_length, false, true);
Liam Miller-Cushon31c88782016-04-11 22:59:37 +0000119 free(classdata_out);
Liam Miller-Cushon31c88782016-04-11 22:59:37 +0000120 }
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000121}
122
tomlu8a56b162018-02-09 10:32:40 -0800123// Copies the string into the buffer without the null terminator, returns length
124static size_t WriteStr(u1 *buf, const char *str) {
125 size_t len = strlen(str);
126 memcpy(buf, str, len);
127 return len;
128}
129
cushonf5728452018-03-21 20:30:12 -0700130// Computes the size of zip file content for the manifest created by
131// WriteManifest, including zip file format overhead.
132static size_t EstimateManifestOutputSize(const char *target_label,
133 const char *injecting_rule_kind) {
134 if (target_label == NULL) {
135 return 0;
136 }
137 // local headers
138 size_t length = 30 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
139 // central directory
140 length += 46 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
141 // zip64 EOCD entries
142 length += 56 * 2;
143
144 // manifest content
145 length += MANIFEST_HEADER_LENGTH;
146 // target label manifest entry, including newline
147 length += TARGET_LABEL_KEY_LENGTH + strlen(target_label) + 2;
148 if (injecting_rule_kind) {
149 // injecting rule kind manifest entry, including newline
150 length += INJECTING_RULE_KIND_KEY_LENGTH + strlen(injecting_rule_kind) + 2;
151 }
152 return length;
153}
154
tomlu8a56b162018-02-09 10:32:40 -0800155static void WriteManifest(ZipBuilder *out, const char *target_label,
156 const char *injecting_rule_kind) {
157 if (target_label == NULL) {
158 return;
159 }
cushonf5728452018-03-21 20:30:12 -0700160 out->WriteEmptyFile(MANIFEST_DIR_PATH);
161 u1 *start = out->NewFile(MANIFEST_PATH, 0);
tomlu8a56b162018-02-09 10:32:40 -0800162 u1 *buf = start;
163 buf += WriteStr(buf, MANIFEST_HEADER);
164 buf += WriteStr(buf, TARGET_LABEL_KEY);
165 buf += WriteStr(buf, target_label);
cushonf5728452018-03-21 20:30:12 -0700166 *buf++ = '\r';
tomlu8a56b162018-02-09 10:32:40 -0800167 *buf++ = '\n';
168 if (injecting_rule_kind) {
169 buf += WriteStr(buf, INJECTING_RULE_KIND_KEY);
170 buf += WriteStr(buf, injecting_rule_kind);
cushonf5728452018-03-21 20:30:12 -0700171 *buf++ = '\r';
tomlu8a56b162018-02-09 10:32:40 -0800172 *buf++ = '\n';
173 }
174 size_t total_len = buf - start;
175 out->FinishFile(total_len);
176}
177
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000178// Opens "file_in" (a .jar file) for reading, and writes an interface
179// .jar to "file_out".
tomlu8a56b162018-02-09 10:32:40 -0800180static void OpenFilesAndProcessJar(const char *file_out, const char *file_in,
181 const char *target_label,
182 const char *injecting_rule_kind) {
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000183 JarStripperProcessor processor;
184 std::unique_ptr<ZipExtractor> in(ZipExtractor::Create(file_in, &processor));
185 if (in.get() == NULL) {
186 fprintf(stderr, "Unable to open Zip file %s: %s\n", file_in,
187 strerror(errno));
188 abort();
189 }
cushonf5728452018-03-21 20:30:12 -0700190 u8 output_length =
191 in->CalculateOutputLength() +
192 EstimateManifestOutputSize(target_label, injecting_rule_kind);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000193 std::unique_ptr<ZipBuilder> out(ZipBuilder::Create(file_out, output_length));
194 if (out.get() == NULL) {
195 fprintf(stderr, "Unable to open output file %s: %s\n", file_out,
196 strerror(errno));
197 abort();
198 }
199 processor.SetZipBuilder(out.get());
200
cushonfd953702018-03-22 20:09:26 -0700201 WriteManifest(out.get(), target_label, injecting_rule_kind);
202
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000203 // Process all files in the zip
204 if (in->ProcessAll() < 0) {
205 fprintf(stderr, "%s\n", in->GetError());
206 abort();
207 }
208
209 // Add dummy file, since javac doesn't like truly empty jars.
210 if (out->GetNumberFiles() == 0) {
211 out->WriteEmptyFile("dummy");
212 }
213 // Finish writing the output file
214 if (out->Finish() < 0) {
215 fprintf(stderr, "%s\n", out->GetError());
216 abort();
217 }
218 // Get all file size
219 size_t in_length = in->GetSize();
220 size_t out_length = out->GetSize();
221 if (verbose) {
222 fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n",
223 file_in, file_out,
224 static_cast<int>(100.0 * out_length / in_length));
225 }
226}
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000227} // namespace devtools_ijar
228
229//
230// main method
231//
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100232static void usage() {
tomlu8a56b162018-02-09 10:32:40 -0800233 fprintf(stderr,
234 "Usage: ijar "
235 "[-v] [--target label label] [--injecting_rule_kind kind] "
236 "x.jar [x_interface.jar>]\n");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237 fprintf(stderr, "Creates an interface jar from the specified jar file.\n");
238 exit(1);
239}
240
241int main(int argc, char **argv) {
tomlu8a56b162018-02-09 10:32:40 -0800242 const char *target_label = NULL;
243 const char *injecting_rule_kind = NULL;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100244 const char *filename_in = NULL;
245 const char *filename_out = NULL;
246
247 for (int ii = 1; ii < argc; ++ii) {
248 if (strcmp(argv[ii], "-v") == 0) {
249 devtools_ijar::verbose = true;
tomlu8a56b162018-02-09 10:32:40 -0800250 } else if (strcmp(argv[ii], "--target_label") == 0) {
251 if (++ii >= argc) {
252 usage();
253 }
254 target_label = argv[ii];
255 } else if (strcmp(argv[ii], "--injecting_rule_kind") == 0) {
256 if (++ii >= argc) {
257 usage();
258 }
259 injecting_rule_kind = argv[ii];
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100260 } else if (filename_in == NULL) {
261 filename_in = argv[ii];
262 } else if (filename_out == NULL) {
263 filename_out = argv[ii];
264 } else {
265 usage();
266 }
267 }
268
269 if (filename_in == NULL) {
270 usage();
271 }
272
273 // Guess output filename from input:
274 char filename_out_buf[PATH_MAX];
275 if (filename_out == NULL) {
276 size_t len = strlen(filename_in);
277 if (len > 4 && strncmp(filename_in + len - 4, ".jar", 4) == 0) {
278 strcpy(filename_out_buf, filename_in);
279 strcpy(filename_out_buf + len - 4, "-interface.jar");
280 filename_out = filename_out_buf;
281 } else {
282 fprintf(stderr, "Can't determine output filename since input filename "
283 "doesn't end with '.jar'.\n");
284 return 1;
285 }
286 }
287
288 if (devtools_ijar::verbose) {
289 fprintf(stderr, "INFO: writing to '%s'.\n", filename_out);
290 }
291
tomlu8a56b162018-02-09 10:32:40 -0800292 devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in, target_label,
293 injecting_rule_kind);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000294 return 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295}