JImageTask.java revision 13901:b2a69d66dc65
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 26package jdk.tools.jimage; 27 28import java.io.File; 29import java.io.IOException; 30import java.io.PrintWriter; 31import java.nio.ByteBuffer; 32import java.nio.ByteOrder; 33import java.nio.channels.FileChannel; 34import java.nio.file.Files; 35import java.nio.file.Path; 36import static java.nio.file.StandardOpenOption.READ; 37import static java.nio.file.StandardOpenOption.WRITE; 38import java.util.LinkedList; 39import java.util.List; 40import jdk.internal.jimage.BasicImageReader; 41import jdk.internal.jimage.ImageHeader; 42import static jdk.internal.jimage.ImageHeader.MAGIC; 43import static jdk.internal.jimage.ImageHeader.MAJOR_VERSION; 44import static jdk.internal.jimage.ImageHeader.MINOR_VERSION; 45import jdk.internal.jimage.ImageLocation; 46import jdk.tools.jlink.internal.ImageResourcesTree; 47import jdk.tools.jlink.internal.ImagePluginConfiguration; 48import jdk.tools.jlink.internal.ImagePluginStack; 49import jdk.tools.jlink.internal.TaskHelper; 50import jdk.tools.jlink.internal.TaskHelper.BadArgs; 51import static jdk.tools.jlink.internal.TaskHelper.JIMAGE_BUNDLE; 52import jdk.tools.jlink.internal.TaskHelper.Option; 53import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; 54 55class JImageTask { 56 57 static final Option<?>[] recognizedOptions = { 58 new Option<JImageTask>(true, (task, opt, arg) -> { 59 task.options.directory = arg; 60 }, "--dir"), 61 new Option<JImageTask>(false, (task, opt, arg) -> { 62 task.options.fullVersion = true; 63 }, true, "--fullversion"), 64 new Option<JImageTask>(false, (task, opt, arg) -> { 65 task.options.help = true; 66 }, "--help"), 67 new Option<JImageTask>(true, (task, opt, arg) -> { 68 task.options.flags = arg; 69 }, "--flags"), 70 new Option<JImageTask>(false, (task, opt, arg) -> { 71 task.options.verbose = true; 72 }, "--verbose"), 73 new Option<JImageTask>(false, (task, opt, arg) -> { 74 task.options.version = true; 75 }, "--version") 76 }; 77 private static final TaskHelper taskHelper 78 = new TaskHelper(JIMAGE_BUNDLE); 79 private static final OptionsHelper<JImageTask> optionsHelper 80 = taskHelper.newOptionsHelper(JImageTask.class, recognizedOptions); 81 82 static class OptionsValues { 83 Task task = Task.LIST; 84 String directory = "."; 85 boolean fullVersion; 86 boolean help; 87 String flags; 88 boolean verbose; 89 boolean version; 90 List<File> jimages = new LinkedList<>(); 91 } 92 93 private static final String PROGNAME = "jimage"; 94 private final OptionsValues options = new OptionsValues(); 95 96 enum Task { 97 EXTRACT, 98 INFO, 99 LIST, 100 RECREATE, 101 SET, 102 VERIFY 103 }; 104 105 private String pad(String string, int width, boolean justifyRight) { 106 int length = string.length(); 107 108 if (length == width) { 109 return string; 110 } 111 112 if (length > width) { 113 return string.substring(0, width); 114 } 115 116 int padding = width - length; 117 118 StringBuilder sb = new StringBuilder(width); 119 if (justifyRight) { 120 for (int i = 0; i < padding; i++) { 121 sb.append(' '); 122 } 123 } 124 125 sb.append(string); 126 127 if (!justifyRight) { 128 for (int i = 0; i < padding; i++) { 129 sb.append(' '); 130 } 131 } 132 133 return sb.toString(); 134 } 135 136 private String pad(String string, int width) { 137 return pad(string, width, false); 138 } 139 140 private String pad(long value, int width) { 141 return pad(Long.toString(value), width, true); 142 } 143 144 private static final int EXIT_OK = 0; // No errors. 145 private static final int EXIT_ERROR = 1; // Completed but reported errors. 146 private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches. 147 private static final int EXIT_SYSERR = 3; // System error or resource exhaustion. 148 private static final int EXIT_ABNORMAL = 4; // Terminated abnormally. 149 150 int run(String[] args) { 151 if (log == null) { 152 setLog(new PrintWriter(System.out)); 153 } 154 155 try { 156 List<String> unhandled = optionsHelper.handleOptions(this, args); 157 if(!unhandled.isEmpty()) { 158 options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase()); 159 for(int i = 1; i < unhandled.size(); i++) { 160 options.jimages.add(new File(unhandled.get(i))); 161 } 162 } 163 if (options.help) { 164 optionsHelper.showHelp(PROGNAME); 165 } 166 if(optionsHelper.listPlugins()) { 167 optionsHelper.listPlugins(true); 168 return EXIT_OK; 169 } 170 if (options.version || options.fullVersion) { 171 taskHelper.showVersion(options.fullVersion); 172 } 173 boolean ok = run(); 174 return ok ? EXIT_OK : EXIT_ERROR; 175 } catch (BadArgs e) { 176 taskHelper.reportError(e.key, e.args); 177 if (e.showUsage) { 178 log.println(taskHelper.getMessage("main.usage.summary", PROGNAME)); 179 } 180 return EXIT_CMDERR; 181 } catch (Exception x) { 182 x.printStackTrace(); 183 return EXIT_ABNORMAL; 184 } finally { 185 log.flush(); 186 } 187 } 188 189 private void recreate() throws Exception, BadArgs { 190 File directory = new File(options.directory); 191 if (!directory.isDirectory()) { 192 throw taskHelper.newBadArgs("err.not.a.dir", directory.getAbsolutePath()); 193 } 194 Path dirPath = directory.toPath(); 195 if (options.jimages.isEmpty()) { 196 throw taskHelper.newBadArgs("err.jimage.not.specified"); 197 } else if (options.jimages.size() != 1) { 198 throw taskHelper.newBadArgs("err.only.one.jimage"); 199 } 200 201 Path jimage = options.jimages.get(0).toPath(); 202 203 if (jimage.toFile().createNewFile()) { 204 ImagePluginStack pc = ImagePluginConfiguration.parseConfiguration(taskHelper. 205 getPluginsConfig(null, false)); 206 ExtractedImage img = new ExtractedImage(dirPath, pc, log, options.verbose); 207 img.recreateJImage(jimage); 208 } else { 209 throw taskHelper.newBadArgs("err.jimage.already.exists", jimage.getFileName()); 210 } 211 } 212 213 private void title(File file, BasicImageReader reader) { 214 log.println("jimage: " + file.getName()); 215 } 216 217 private void listTitle(File file, BasicImageReader reader) { 218 title(file, reader); 219 220 if (options.verbose) { 221 log.print(pad("Offset", OFFSET_WIDTH + 1)); 222 log.print(pad("Size", SIZE_WIDTH + 1)); 223 log.print(pad("Compressed", COMPRESSEDSIZE_WIDTH + 1)); 224 log.println(" Entry"); 225 } 226 } 227 228 private interface JImageAction { 229 public void apply(File file, BasicImageReader reader) throws IOException, BadArgs; 230 } 231 232 private interface ResourceAction { 233 public void apply(BasicImageReader reader, String name, 234 ImageLocation location) throws IOException, BadArgs; 235 } 236 237 private void extract(BasicImageReader reader, String name, 238 ImageLocation location) throws IOException, BadArgs { 239 File directory = new File(options.directory); 240 byte[] bytes = reader.getResource(location); 241 File resource = new File(directory, name); 242 File parent = resource.getParentFile(); 243 244 if (parent.exists()) { 245 if (!parent.isDirectory()) { 246 throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 247 } 248 } else if (!parent.mkdirs()) { 249 throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 250 } 251 252 if (!ImageResourcesTree.isTreeInfoResource(name)) { 253 Files.write(resource.toPath(), bytes); 254 } 255 } 256 257 private static final int NUMBER_WIDTH = 12; 258 private static final int OFFSET_WIDTH = NUMBER_WIDTH; 259 private static final int SIZE_WIDTH = NUMBER_WIDTH; 260 private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH; 261 262 private void print(String entry, ImageLocation location) { 263 log.print(pad(location.getContentOffset(), OFFSET_WIDTH) + " "); 264 log.print(pad(location.getUncompressedSize(), SIZE_WIDTH) + " "); 265 log.print(pad(location.getCompressedSize(), COMPRESSEDSIZE_WIDTH) + " "); 266 log.println(entry); 267 } 268 269 private void print(BasicImageReader reader, String entry) { 270 if (options.verbose) { 271 print(entry, reader.findLocation(entry)); 272 } else { 273 log.println(entry); 274 } 275 } 276 277 private void info(File file, BasicImageReader reader) throws IOException { 278 ImageHeader header = reader.getHeader(); 279 280 log.println(" Major Version: " + header.getMajorVersion()); 281 log.println(" Minor Version: " + header.getMinorVersion()); 282 log.println(" Flags: " + Integer.toHexString(header.getMinorVersion())); 283 log.println(" Resource Count: " + header.getResourceCount()); 284 log.println(" Table Length: " + header.getTableLength()); 285 log.println(" Offsets Size: " + header.getOffsetsSize()); 286 log.println(" Redirects Size: " + header.getRedirectSize()); 287 log.println(" Locations Size: " + header.getLocationsSize()); 288 log.println(" Strings Size: " + header.getStringsSize()); 289 log.println(" Index Size: " + header.getIndexSize()); 290 } 291 292 private void list(BasicImageReader reader, String name, ImageLocation location) { 293 print(reader, name); 294 } 295 296 void set(File file, BasicImageReader reader) throws BadArgs { 297 try { 298 ImageHeader oldHeader = reader.getHeader(); 299 300 int value = 0; 301 try { 302 value = Integer.valueOf(options.flags); 303 } catch (NumberFormatException ex) { 304 throw taskHelper.newBadArgs("err.flags.not.int", options.flags); 305 } 306 307 ImageHeader newHeader = new ImageHeader(MAGIC, MAJOR_VERSION, MINOR_VERSION, 308 value, 309 oldHeader.getResourceCount(), oldHeader.getTableLength(), 310 oldHeader.getLocationsSize(), oldHeader.getStringsSize()); 311 312 ByteBuffer buffer = ByteBuffer.allocate(ImageHeader.getHeaderSize()); 313 buffer.order(ByteOrder.nativeOrder()); 314 newHeader.writeTo(buffer); 315 buffer.rewind(); 316 317 try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE)) { 318 channel.write(buffer, 0); 319 } 320 } catch (IOException ex) { 321 throw taskHelper.newBadArgs("err.cannot.update.file", file.getName()); 322 } 323 } 324 325 void verify(BasicImageReader reader, String name, ImageLocation location) { 326 if (name.endsWith(".class")) { 327 byte[] bytes = reader.getResource(location); 328 329 if (bytes == null || bytes.length <= 4 || 330 (bytes[0] & 0xFF) != 0xCA || 331 (bytes[1] & 0xFF) != 0xFE || 332 (bytes[2] & 0xFF) != 0xBA || 333 (bytes[3] & 0xFF) != 0xBE) { 334 log.print(" NOT A CLASS: "); 335 print(reader, name); 336 } 337 } 338 } 339 340 private void iterate(JImageAction jimageAction, 341 ResourceAction resourceAction) throws IOException, BadArgs { 342 for (File file : options.jimages) { 343 if (!file.exists() || !file.isFile()) { 344 throw taskHelper.newBadArgs("err.not.a.jimage", file.getName()); 345 } 346 347 try (BasicImageReader reader = BasicImageReader.open(file.toPath())) { 348 if (jimageAction != null) { 349 jimageAction.apply(file, reader); 350 } 351 352 if (resourceAction != null) { 353 String[] entryNames = reader.getEntryNames(); 354 355 for (String name : entryNames) { 356 if (!ImageResourcesTree.isTreeInfoResource(name)) { 357 ImageLocation location = reader.findLocation(name); 358 resourceAction.apply(reader, name, location); 359 } 360 } 361 } 362 } 363 } 364 } 365 366 private boolean run() throws Exception, BadArgs { 367 switch (options.task) { 368 case EXTRACT: 369 iterate(null, this::extract); 370 break; 371 case INFO: 372 iterate(this::info, null); 373 break; 374 case LIST: 375 iterate(this::listTitle, this::list); 376 break; 377 case RECREATE: 378 recreate(); 379 break; 380 case SET: 381 iterate(this::set, null); 382 break; 383 case VERIFY: 384 iterate(this::title, this::verify); 385 break; 386 default: 387 throw taskHelper.newBadArgs("err.invalid.task", options.task.name()).showUsage(true); 388 } 389 return true; 390 } 391 392 private PrintWriter log; 393 void setLog(PrintWriter out) { 394 log = out; 395 taskHelper.setLog(log); 396 } 397} 398