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 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.getName()); 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 } 435 } 436 } 437 438 private boolean run() throws Exception, BadArgs { 439 switch (options.task) { 440 case EXTRACT: 441 iterate(null, null, this::extract); 442 break; 443 case INFO: 444 iterate(this::info, null, null); 445 break; 446 case LIST: 447 iterate(this::listTitle, this::listModule, this::list); 448 break; 449 case VERIFY: 450 iterate(this::listTitle, null, this::verify); 451 break; 452 default: 453 throw TASK_HELPER.newBadArgs("err.not.a.task", 454 options.task.name()).showUsage(true); 455 } 456 return true; 457 } 458 }