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