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