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