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 
JImageTask()94     JImageTask() {
95         this.options = new OptionsValues();
96         this.includePredicates = new ArrayList<>();
97         log = null;
98     }
99 
setLog(PrintWriter out)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 
pad(String string, int width, boolean justifyRight)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 
pad(String string, int width)154     private String pad(String string, int width) {
155         return pad(string, width, false);
156     }
157 
pad(long value, int width)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 
run(String[] args)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 
processInclude(String include)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 
listTitle(File file, BasicImageReader reader)267     private void listTitle(File file, BasicImageReader reader) {
268         log.println("jimage: " + file);
269     }
270 
271     private interface JImageAction {
apply(File file, BasicImageReader reader)272         public void apply(File file, BasicImageReader reader) throws IOException, BadArgs;
273     }
274 
275     private interface ModuleAction {
apply(BasicImageReader reader, String oldModule, String newModule)276          public void apply(BasicImageReader reader,
277                  String oldModule, String newModule) throws IOException, BadArgs;
278     }
279 
280     private interface ResourceAction {
apply(BasicImageReader reader, String name, ImageLocation location)281         public void apply(BasicImageReader reader, String name,
282                 ImageLocation location) throws IOException, BadArgs;
283     }
284 
extract(BasicImageReader reader, String name, ImageLocation location)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 
trimModule(String name)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 
print(String name, ImageLocation location)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 
print(BasicImageReader reader, String name)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 
info(File file, BasicImageReader reader)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 
listModule(BasicImageReader reader, String oldModule, String newModule)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 
list(BasicImageReader reader, String name, ImageLocation location)363     private void list(BasicImageReader reader, String name, ImageLocation location) {
364         print(reader, name);
365     }
366 
verify(BasicImageReader reader, String name, ImageLocation location)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 
iterate(JImageAction jimageAction, ModuleAction moduleAction, ResourceAction resourceAction)380     private void iterate(JImageAction jimageAction,
381             ModuleAction moduleAction,
382             ResourceAction resourceAction) throws IOException, BadArgs {
383         if (options.jimages.isEmpty()) {
384             throw TASK_HELPER.newBadArgs("err.no.jimage");
385         }
386 
387         for (File file : options.jimages) {
388             if (!file.exists() || !file.isFile()) {
389                 throw TASK_HELPER.newBadArgs("err.not.a.jimage", file);
390             }
391 
392             try (BasicImageReader reader = BasicImageReader.open(file.toPath())) {
393                 if (jimageAction != null) {
394                     jimageAction.apply(file, reader);
395                 }
396 
397                 if (resourceAction != null) {
398                     String[] entryNames = reader.getEntryNames();
399                     String oldModule = "";
400 
401                     for (String name : entryNames) {
402                         boolean match = includePredicates.isEmpty();
403 
404                         for (Predicate<String> predicate : includePredicates) {
405                             if (predicate.test(name)) {
406                                 match = true;
407                                 break;
408                             }
409                         }
410 
411                         if (!match) {
412                             continue;
413                         }
414 
415                         if (!ImageResourcesTree.isTreeInfoResource(name)) {
416                             if (moduleAction != null) {
417                                 int offset = name.indexOf('/', 1);
418 
419                                 String newModule = offset != -1 ?
420                                         name.substring(1, offset) :
421                                         "<unknown>";
422 
423                                 if (!oldModule.equals(newModule)) {
424                                     moduleAction.apply(reader, oldModule, newModule);
425                                     oldModule = newModule;
426                                 }
427                             }
428 
429                             ImageLocation location = reader.findLocation(name);
430                             resourceAction.apply(reader, name, location);
431                         }
432                     }
433                 }
434             } catch (IOException ioe) {
435                 throw TASK_HELPER.newBadArgs("err.invalid.jimage", file, ioe.getMessage());
436             }
437         }
438     }
439 
run()440     private boolean run() throws Exception, BadArgs {
441         switch (options.task) {
442             case EXTRACT:
443                 iterate(null, null, this::extract);
444                 break;
445             case INFO:
446                 iterate(this::info, null, null);
447                 break;
448             case LIST:
449                 iterate(this::listTitle, this::listModule, this::list);
450                 break;
451             case VERIFY:
452                 iterate(this::listTitle, null, this::verify);
453                 break;
454             default:
455                 throw TASK_HELPER.newBadArgs("err.not.a.task",
456                         options.task.name()).showUsage(true);
457         }
458         return true;
459     }
460 }
461