1 /*
2 * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.tools.jimage;
27
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.nio.file.FileSystem;
32 import java.nio.file.Files;
33 import java.nio.file.PathMatcher;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.MissingResourceException;
40 import java.util.function.Predicate;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43
44 import jdk.internal.jimage.BasicImageReader;
45 import jdk.internal.jimage.ImageHeader;
46 import jdk.internal.jimage.ImageLocation;
47 import jdk.internal.org.objectweb.asm.ClassReader;
48 import jdk.internal.org.objectweb.asm.tree.ClassNode;
49 import jdk.tools.jlink.internal.ImageResourcesTree;
50 import jdk.tools.jlink.internal.TaskHelper;
51 import jdk.tools.jlink.internal.TaskHelper.BadArgs;
52 import static jdk.tools.jlink.internal.TaskHelper.JIMAGE_BUNDLE;
53 import jdk.tools.jlink.internal.TaskHelper.Option;
54 import jdk.tools.jlink.internal.TaskHelper.OptionsHelper;
55 import jdk.tools.jlink.internal.Utils;
56
57 class JImageTask {
58 private static final Option<?>[] RECOGNIZED_OPTIONS = {
59 new Option<JImageTask>(true, (task, option, arg) -> {
60 task.options.directory = arg;
61 }, "--dir"),
62
63 new Option<JImageTask>(true, (task, option, arg) -> {
64 task.options.include = arg;
65 }, "--include"),
66
67 new Option<JImageTask>(false, (task, option, arg) -> {
68 task.options.fullVersion = true;
69 }, true, "--full-version"),
70
71 new Option<JImageTask>(false, (task, option, arg) -> {
72 task.options.help = true;
73 }, "--help", "-h", "-?"),
74
75 new Option<JImageTask>(false, (task, option, arg) -> {
76 task.options.verbose = true;
77 }, "--verbose"),
78
79 new Option<JImageTask>(false, (task, option, arg) -> {
80 task.options.version = true;
81 }, "--version")
82 };
83 private static final TaskHelper TASK_HELPER
84 = new TaskHelper(JIMAGE_BUNDLE);
85 private static final OptionsHelper<JImageTask> OPTION_HELPER
86 = TASK_HELPER.newOptionsHelper(JImageTask.class, RECOGNIZED_OPTIONS);
87 private static final String PROGNAME = "jimage";
88 private static final FileSystem JRT_FILE_SYSTEM = Utils.jrtFileSystem();
89
90 private final OptionsValues options;
91 private final List<Predicate<String>> includePredicates;
92 private PrintWriter log;
93
94 JImageTask() {
95 this.options = new OptionsValues();
96 this.includePredicates = new ArrayList<>();
97 log = null;
98 }
99
100 void setLog(PrintWriter out) {
101 log = out;
102 TASK_HELPER.setLog(log);
103 }
104
105 static class OptionsValues {
106 Task task = null;
107 String directory = ".";
108 String include = "";
109 boolean fullVersion;
110 boolean help;
111 boolean verbose;
112 boolean version;
113 List<File> jimages = new LinkedList<>();
114 }
115
116 enum Task {
117 EXTRACT,
118 INFO,
119 LIST,
120 VERIFY
121 };
122
123 private String pad(String string, int width, boolean justifyRight) {
124 int length = string.length();
125
126 if (length == width) {
127 return string;
128 }
129
130 if (length > width) {
131 return string.substring(0, width);
132 }
133
134 int padding = width - length;
135
136 StringBuilder sb = new StringBuilder(width);
137 if (justifyRight) {
138 for (int i = 0; i < padding; i++) {
139 sb.append(' ');
140 }
141 }
142
143 sb.append(string);
144
145 if (!justifyRight) {
146 for (int i = 0; i < padding; i++) {
147 sb.append(' ');
148 }
149 }
150
151 return sb.toString();
152 }
153
154 private String pad(String string, int width) {
155 return pad(string, width, false);
156 }
157
158 private String pad(long value, int width) {
159 return pad(Long.toString(value), width, true);
160 }
161
162 private static final int EXIT_OK = 0; // No errors.
163 private static final int EXIT_ERROR = 1; // Completed but reported errors.
164 private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches.
165 private static final int EXIT_SYSERR = 3; // System error or resource exhaustion.
166 private static final int EXIT_ABNORMAL = 4; // Terminated abnormally.
167
168 int run(String[] args) {
169 if (log == null) {
170 setLog(new PrintWriter(System.out, true));
171 }
172
173 if (args.length == 0) {
174 log.println(TASK_HELPER.getMessage("main.usage.summary", PROGNAME));
175 return EXIT_ABNORMAL;
176 }
177
178 try {
179 String command;
180 String[] remaining = args;
181 try {
182 command = args[0];
183 options.task = Enum.valueOf(Task.class, args[0].toUpperCase(Locale.ENGLISH));
184 remaining = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length)
185 : new String[0];
186 } catch (IllegalArgumentException ex) {
187 command = null;
188 options.task = null;
189 }
190
191 // process arguments
192 List<String> unhandled = OPTION_HELPER.handleOptions(this, remaining);
193 for (String f : unhandled) {
194 options.jimages.add(new File(f));
195 }
196
197 if (options.task == null && !options.help && !options.version && !options.fullVersion) {
198 throw TASK_HELPER.newBadArgs("err.not.a.task",
199 command != null ? command : "<unspecified>");
200 }
201
202 if (options.help) {
203 if (options.task == null) {
204 log.println(TASK_HELPER.getMessage("main.usage", PROGNAME));
205 Arrays.asList(RECOGNIZED_OPTIONS).stream()
206 .filter(option -> !option.isHidden())
207 .sorted()
208 .forEach(option -> {
209 log.println(TASK_HELPER.getMessage(option.resourceName()));
210 });
211 log.println(TASK_HELPER.getMessage("main.opt.footer"));
212 } else {
213 try {
214 log.println(TASK_HELPER.getMessage("main.usage." +
215 options.task.toString().toLowerCase()));
216 } catch (MissingResourceException ex) {
217 throw TASK_HELPER.newBadArgs("err.not.a.task", command);
218 }
219 }
220 return EXIT_OK;
221 }
222
223 if (options.version || options.fullVersion) {
224 if (options.task == null && !unhandled.isEmpty()) {
225 throw TASK_HELPER.newBadArgs("err.not.a.task",
226 Stream.of(args).collect(Collectors.joining(" ")));
227 }
228
229 TASK_HELPER.showVersion(options.fullVersion);
230 if (unhandled.isEmpty()) {
231 return EXIT_OK;
232 }
233 }
234
235 processInclude(options.include);
236
237 return run() ? EXIT_OK : EXIT_ERROR;
238 } catch (BadArgs e) {
239 TASK_HELPER.reportError(e.key, e.args);
240
241 if (e.showUsage) {
242 log.println(TASK_HELPER.getMessage("main.usage.summary", PROGNAME));
243 }
244
245 return EXIT_CMDERR;
246 } catch (Exception x) {
247 x.printStackTrace();
248
249 return EXIT_ABNORMAL;
250 } finally {
251 log.flush();
252 }
253 }
254
255 private void processInclude(String include) {
256 if (include.isEmpty()) {
257 return;
258 }
259
260 for (String filter : include.split(",")) {
261 final PathMatcher matcher = Utils.getPathMatcher(JRT_FILE_SYSTEM, filter);
262 Predicate<String> predicate = (path) -> matcher.matches(JRT_FILE_SYSTEM.getPath(path));
263 includePredicates.add(predicate);
264 }
265 }
266
267 private void listTitle(File file, BasicImageReader reader) {
268 log.println("jimage: " + file);
269 }
270
271 private interface JImageAction {
272 public void apply(File file, BasicImageReader reader) throws IOException, BadArgs;
273 }
274
275 private interface ModuleAction {
276 public void apply(BasicImageReader reader,
277 String oldModule, String newModule) throws IOException, BadArgs;
278 }
279
280 private interface ResourceAction {
281 public void apply(BasicImageReader reader, String name,
282 ImageLocation location) throws IOException, BadArgs;
283 }
284
285 private void extract(BasicImageReader reader, String name,
286 ImageLocation location) throws IOException, BadArgs {
287 File directory = new File(options.directory);
288 byte[] bytes = reader.getResource(location);
289 File resource = new File(directory, name);
290 File parent = resource.getParentFile();
291
292 if (parent.exists()) {
293 if (!parent.isDirectory()) {
294 throw TASK_HELPER.newBadArgs("err.cannot.create.dir",
295 parent.getAbsolutePath());
296 }
297 } else if (!parent.mkdirs()) {
298 throw TASK_HELPER.newBadArgs("err.cannot.create.dir",
299 parent.getAbsolutePath());
300 }
301
302 if (!ImageResourcesTree.isTreeInfoResource(name)) {
303 Files.write(resource.toPath(), bytes);
304 }
305 }
306
307 private static final int OFFSET_WIDTH = 12;
308 private static final int SIZE_WIDTH = 10;
309 private static final int COMPRESSEDSIZE_WIDTH = 10;
310
311 private String trimModule(String name) {
312 int offset = name.indexOf('/', 1);
313
314 if (offset != -1 && offset + 1 < name.length()) {
315 return name.substring(offset + 1);
316 }
317
318 return name;
319 }
320
321 private void print(String name, ImageLocation location) {
322 log.print(pad(location.getContentOffset(), OFFSET_WIDTH) + " ");
323 log.print(pad(location.getUncompressedSize(), SIZE_WIDTH) + " ");
324 log.print(pad(location.getCompressedSize(), COMPRESSEDSIZE_WIDTH) + " ");
325 log.println(trimModule(name));
326 }
327
328 private void print(BasicImageReader reader, String name) {
329 if (options.verbose) {
330 print(name, reader.findLocation(name));
331 } else {
332 log.println(" " + trimModule(name));
333 }
334 }
335
336 private void info(File file, BasicImageReader reader) throws IOException {
337 ImageHeader header = reader.getHeader();
338
339 log.println(" Major Version: " + header.getMajorVersion());
340 log.println(" Minor Version: " + header.getMinorVersion());
341 log.println(" Flags: " + Integer.toHexString(header.getFlags()));
342 log.println(" Resource Count: " + header.getResourceCount());
343 log.println(" Table Length: " + header.getTableLength());
344 log.println(" Offsets Size: " + header.getOffsetsSize());
345 log.println(" Redirects Size: " + header.getRedirectSize());
346 log.println(" Locations Size: " + header.getLocationsSize());
347 log.println(" Strings Size: " + header.getStringsSize());
348 log.println(" Index Size: " + header.getIndexSize());
349 }
350
351 private void listModule(BasicImageReader reader, String oldModule, String newModule) {
352 log.println();
353 log.println("Module: " + newModule);
354
355 if (options.verbose) {
356 log.print(pad("Offset", OFFSET_WIDTH) + " ");
357 log.print(pad("Size", SIZE_WIDTH) + " ");
358 log.print(pad("Compressed", COMPRESSEDSIZE_WIDTH) + " ");
359 log.println("Entry");
360 }
361 }
362
363 private void list(BasicImageReader reader, String name, ImageLocation location) {
364 print(reader, name);
365 }
366
367 void verify(BasicImageReader reader, String name, ImageLocation location) {
368 if (name.endsWith(".class") && !name.endsWith("module-info.class")) {
369 try {
370 byte[] bytes = reader.getResource(location);
371 ClassReader cr = new ClassReader(bytes);
372 ClassNode cn = new ClassNode();
373 cr.accept(cn, 0);
374 } catch (Exception ex) {
375 log.println("Error(s) in Class: " + name);
376 }
377 }
378 }
379
380 private void iterate(JImageAction jimageAction,
381 ModuleAction moduleAction,
382 ResourceAction resourceAction) throws IOException, BadArgs {
383 validateOptions();
384
385 for (File file : options.jimages) {
386 if (!file.exists() || !file.isFile()) {
387 throw TASK_HELPER.newBadArgs("err.not.a.jimage", file.getName());
388 }
389
390 try (BasicImageReader reader = BasicImageReader.open(file.toPath())) {
391 if (jimageAction != null) {
392 jimageAction.apply(file, reader);
393 }
394
395 if (resourceAction != null) {
396 String[] entryNames = reader.getEntryNames();
397 String oldModule = "";
398
399 for (String name : entryNames) {
400 boolean match = includePredicates.isEmpty();
401
402 for (Predicate<String> predicate : includePredicates) {
403 if (predicate.test(name)) {
404 match = true;
405 break;
406 }
407 }
408
409 if (!match) {
410 continue;
411 }
412
413 if (!ImageResourcesTree.isTreeInfoResource(name)) {
414 if (moduleAction != null) {
415 int offset = name.indexOf('/', 1);
416
417 String newModule = offset != -1 ?
418 name.substring(1, offset) :
419 "<unknown>";
420
421 if (!oldModule.equals(newModule)) {
422 moduleAction.apply(reader, oldModule, newModule);
423 oldModule = newModule;
424 }
425 }
426
427 ImageLocation location = reader.findLocation(name);
428 resourceAction.apply(reader, name, location);
429 }
430 }
431 }
432 }
433 }
434 }
435
436 private void validateOptions() throws BadArgs {
437 if (options.jimages.isEmpty()) {
438 throw TASK_HELPER.newBadArgs("err.no.jimage");
439 }
440
441 if (options.task == Task.EXTRACT) {
442 File extractTargetDir = new File(options.directory);
443 if (extractTargetDir.exists()) {
444 if (extractTargetDir.isDirectory()) {
445 if (extractTargetDir.listFiles().length > 0) {
446 throw TASK_HELPER.newBadArgs("err.not.an.empty.dir", options.directory);
447 }
448 } else {
449 throw TASK_HELPER.newBadArgs("err.not.a.dir", options.directory);
450 }
451 }
452 }
453 }
454
455 private boolean run() throws Exception, BadArgs {
456 switch (options.task) {
457 case EXTRACT:
458 iterate(null, null, this::extract);
459 break;
460 case INFO:
461 iterate(this::info, null, null);
462 break;
463 case LIST:
464 iterate(this::listTitle, this::listModule, this::list);
465 break;
466 case VERIFY:
467 iterate(this::listTitle, null, this::verify);
468 break;
469 default:
470 throw TASK_HELPER.newBadArgs("err.not.a.task",
471 options.task.name()).showUsage(true);
472 }
473 return true;
474 }
475 }