1 /* 2 * Copyright (c) 2003, 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 com.sun.java.util.jar.pack; 27 28 import com.sun.java.util.jar.pack.Attribute.Layout; 29 import java.io.BufferedInputStream; 30 import java.io.ByteArrayInputStream; 31 import java.io.ByteArrayOutputStream; 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.time.ZoneOffset; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.ListIterator; 43 import java.util.Map; 44 import java.util.SortedMap; 45 import java.util.jar.JarEntry; 46 import java.util.jar.JarFile; 47 import java.util.jar.JarInputStream; 48 import java.util.jar.Pack200; 49 50 51 /* 52 * Implementation of the Pack provider. 53 * </pre></blockquote> 54 * @author John Rose 55 * @author Kumar Srinivasan 56 */ 57 58 59 public class PackerImpl extends TLGlobals implements Pack200.Packer { 60 61 /** 62 * Constructs a Packer object and sets the initial state of 63 * the packer engines. 64 */ 65 public PackerImpl() {} 66 67 /** 68 * Get the set of options for the pack and unpack engines. 69 * @return A sorted association of option key strings to option values. 70 */ 71 public SortedMap<String, String> properties() { 72 return props; 73 } 74 75 //Driver routines 76 77 /** 78 * Takes a JarFile and converts into a pack-stream. 79 * <p> 80 * Closes its input but not its output. (Pack200 archives are appendable.) 81 * @param in a JarFile 82 * @param out an OutputStream 83 * @exception IOException if an error is encountered. 84 */ 85 public synchronized void pack(JarFile in, OutputStream out) throws IOException { 86 assert(Utils.currentInstance.get() == null); 87 try { 88 Utils.currentInstance.set(this); 89 if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { 90 Utils.copyJarFile(in, out); 91 } else { 92 (new DoPack()).run(in, out); 93 } 94 } finally { 95 Utils.currentInstance.set(null); 96 in.close(); 97 } 98 } 99 100 /** 101 * Takes a JarInputStream and converts into a pack-stream. 102 * <p> 103 * Closes its input but not its output. (Pack200 archives are appendable.) 104 * <p> 105 * The modification time and deflation hint attributes are not available, 106 * for the jar-manifest file and the directory containing the file. 107 * 108 * @see #MODIFICATION_TIME 109 * @see #DEFLATION_HINT 110 * @param in a JarInputStream 111 * @param out an OutputStream 112 * @exception IOException if an error is encountered. 113 */ 114 public synchronized void pack(JarInputStream in, OutputStream out) throws IOException { 115 assert(Utils.currentInstance.get() == null); 116 try { 117 Utils.currentInstance.set(this); 118 if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { 119 Utils.copyJarFile(in, out); 120 } else { 121 (new DoPack()).run(in, out); 122 } 123 } finally { 124 Utils.currentInstance.set(null); 125 in.close(); 126 } 127 } 128 129 // All the worker bees..... 130 // The packer worker. 131 private class DoPack { 132 final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); 133 134 { 135 props.setInteger(Pack200.Packer.PROGRESS, 0); 136 if (verbose > 0) Utils.log.info(props.toString()); 137 } 138 139 // Here's where the bits are collected before getting packed, we also 140 // initialize the version numbers now. 141 final Package pkg = new Package(Package.Version.makeVersion(props, "min.class"), 142 Package.Version.makeVersion(props, "max.class"), 143 Package.Version.makeVersion(props, "package")); 144 145 final String unknownAttrCommand; 146 { 147 String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); 148 if (!(Pack200.Packer.STRIP.equals(uaMode) || 149 Pack200.Packer.PASS.equals(uaMode) || 150 Pack200.Packer.ERROR.equals(uaMode))) { 151 throw new RuntimeException("Bad option: " + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = " + uaMode); 152 } 153 unknownAttrCommand = uaMode.intern(); 154 } 155 final String classFormatCommand; 156 { 157 String fmtMode = props.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS); 158 if (!(Pack200.Packer.PASS.equals(fmtMode) || 159 Pack200.Packer.ERROR.equals(fmtMode))) { 160 throw new RuntimeException("Bad option: " + Utils.CLASS_FORMAT_ERROR + " = " + fmtMode); 161 } 162 classFormatCommand = fmtMode.intern(); 163 } 164 165 final Map<Attribute.Layout, Attribute> attrDefs; 166 final Map<Attribute.Layout, String> attrCommands; 167 { 168 Map<Attribute.Layout, Attribute> lattrDefs = new HashMap<>(); 169 Map<Attribute.Layout, String> lattrCommands = new HashMap<>(); 170 String[] keys = { 171 Pack200.Packer.CLASS_ATTRIBUTE_PFX, 172 Pack200.Packer.FIELD_ATTRIBUTE_PFX, 173 Pack200.Packer.METHOD_ATTRIBUTE_PFX, 174 Pack200.Packer.CODE_ATTRIBUTE_PFX 175 }; 176 int[] ctypes = { 177 Constants.ATTR_CONTEXT_CLASS, 178 Constants.ATTR_CONTEXT_FIELD, 179 Constants.ATTR_CONTEXT_METHOD, 180 Constants.ATTR_CONTEXT_CODE 181 }; 182 for (int i = 0; i < ctypes.length; i++) { 183 String pfx = keys[i]; 184 Map<String, String> map = props.prefixMap(pfx); 185 for (String key : map.keySet()) { 186 assert(key.startsWith(pfx)); 187 String name = key.substring(pfx.length()); 188 String layout = props.getProperty(key); 189 Layout lkey = Attribute.keyForLookup(ctypes[i], name); 190 if (Pack200.Packer.STRIP.equals(layout) || 191 Pack200.Packer.PASS.equals(layout) || 192 Pack200.Packer.ERROR.equals(layout)) { 193 lattrCommands.put(lkey, layout.intern()); 194 } else { 195 Attribute.define(lattrDefs, ctypes[i], name, layout); 196 if (verbose > 1) { 197 Utils.log.fine("Added layout for "+Constants.ATTR_CONTEXT_NAME[i]+" attribute "+name+" = "+layout); 198 } 199 assert(lattrDefs.containsKey(lkey)); 200 } 201 } 202 } 203 this.attrDefs = (lattrDefs.isEmpty()) ? null : lattrDefs; 204 this.attrCommands = (lattrCommands.isEmpty()) ? null : lattrCommands; 205 } 206 207 final boolean keepFileOrder 208 = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); 209 final boolean keepClassOrder 210 = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); 211 212 final boolean keepModtime 213 = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); 214 final boolean latestModtime 215 = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); 216 final boolean keepDeflateHint 217 = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT)); 218 { 219 if (!keepModtime && !latestModtime) { 220 int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME); 221 if (modtime != Constants.NO_MODTIME) { 222 pkg.default_modtime = modtime; 223 } 224 } 225 if (!keepDeflateHint) { 226 boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT); 227 if (deflate_hint) { 228 pkg.default_options |= Constants.AO_DEFLATE_HINT; 229 } 230 } 231 } 232 233 long totalOutputSize = 0; 234 int segmentCount = 0; 235 long segmentTotalSize = 0; 236 long segmentSize = 0; // running counter 237 final long segmentLimit; 238 { 239 long limit; 240 if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) 241 limit = -1; 242 else 243 limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT); 244 limit = Math.min(Integer.MAX_VALUE, limit); 245 limit = Math.max(-1, limit); 246 if (limit == -1) 247 limit = Long.MAX_VALUE; 248 segmentLimit = limit; 249 } 250 251 final List<String> passFiles; // parsed pack.pass.file options 252 { 253 // Which class files will be passed through? 254 passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX); 255 for (ListIterator<String> i = passFiles.listIterator(); i.hasNext(); ) { 256 String file = i.next(); 257 if (file == null) { i.remove(); continue; } 258 file = Utils.getJarEntryName(file); // normalize '\\' to '/' 259 if (file.endsWith("/")) 260 file = file.substring(0, file.length()-1); 261 i.set(file); 262 } 263 if (verbose > 0) Utils.log.info("passFiles = " + passFiles); 264 } 265 266 { 267 // Hook for testing: Forces use of special archive modes. 268 int opt = props.getInteger(Utils.COM_PREFIX+"archive.options"); 269 if (opt != 0) 270 pkg.default_options |= opt; 271 } 272 273 // (Done collecting options from props.) 274 275 // Get a new package, based on the old one. 276 private void makeNextPackage() { 277 pkg.reset(); 278 } 279 280 final class InFile { 281 final String name; 282 final JarFile jf; 283 final JarEntry je; 284 final File f; 285 int modtime = Constants.NO_MODTIME; 286 int options; 287 InFile(String name) { 288 this.name = Utils.getJarEntryName(name); 289 this.f = new File(name); 290 this.jf = null; 291 this.je = null; 292 int timeSecs = getModtime(f.lastModified()); 293 if (keepModtime && timeSecs != Constants.NO_MODTIME) { 294 this.modtime = timeSecs; 295 } else if (latestModtime && timeSecs > pkg.default_modtime) { 296 pkg.default_modtime = timeSecs; 297 } 298 } 299 InFile(JarFile jf, JarEntry je) { 300 this.name = Utils.getJarEntryName(je.getName()); 301 this.f = null; 302 this.jf = jf; 303 this.je = je; 304 int timeSecs = (int) je.getTimeLocal() 305 .atOffset(ZoneOffset.UTC) 306 .toEpochSecond(); 307 if (keepModtime && timeSecs != Constants.NO_MODTIME) { 308 this.modtime = timeSecs; 309 } else if (latestModtime && timeSecs > pkg.default_modtime) { 310 pkg.default_modtime = timeSecs; 311 } 312 if (keepDeflateHint && je.getMethod() == JarEntry.DEFLATED) { 313 options |= Constants.FO_DEFLATE_HINT; 314 } 315 } 316 InFile(JarEntry je) { 317 this(null, je); 318 } 319 boolean isClassFile() { 320 if (!name.endsWith(".class")) { 321 return false; 322 } 323 for (String prefix = name;;) { 324 if (passFiles.contains(prefix)) { 325 return false; 326 } 327 int chop = prefix.lastIndexOf('/'); 328 if (chop < 0) { 329 break; 330 } 331 prefix = prefix.substring(0, chop); 332 } 333 return true; 334 } 335 boolean isMetaInfFile() { 336 return name.startsWith("/" + Utils.METAINF) 337 || name.startsWith(Utils.METAINF); 338 } 339 boolean mustProcess() { 340 return !isMetaInfFile() && isClassFile(); 341 } 342 long getInputLength() { 343 long len = (je != null)? je.getSize(): f.length(); 344 assert(len >= 0) : this+".len="+len; 345 // Bump size by pathname length and modtime/def-hint bytes. 346 return Math.max(0, len) + name.length() + 5; 347 } 348 int getModtime(long timeMillis) { 349 // Convert milliseconds to seconds. 350 long seconds = (timeMillis+500) / 1000; 351 if ((int)seconds == seconds) { 352 return (int)seconds; 353 } else { 354 Utils.log.warning("overflow in modtime for "+f); 355 return Constants.NO_MODTIME; 356 } 357 } 358 void copyTo(Package.File file) { 359 if (modtime != Constants.NO_MODTIME) 360 file.modtime = modtime; 361 file.options |= options; 362 } 363 InputStream getInputStream() throws IOException { 364 if (jf != null) 365 return jf.getInputStream(je); 366 else 367 return new FileInputStream(f); 368 } 369 370 public String toString() { 371 return name; 372 } 373 } 374 375 private int nread = 0; // used only if (verbose > 0) 376 private void noteRead(InFile f) { 377 nread++; 378 if (verbose > 2) 379 Utils.log.fine("...read "+f.name); 380 if (verbose > 0 && (nread % 1000) == 0) 381 Utils.log.info("Have read "+nread+" files..."); 382 } 383 384 void run(JarInputStream in, OutputStream out) throws IOException { 385 // First thing we do is get the manifest, as JIS does 386 // not provide the Manifest as an entry. 387 if (in.getManifest() != null) { 388 ByteArrayOutputStream tmp = new ByteArrayOutputStream(); 389 in.getManifest().write(tmp); 390 InputStream tmpIn = new ByteArrayInputStream(tmp.toByteArray()); 391 pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn)); 392 } 393 for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { 394 InFile inFile = new InFile(je); 395 396 String name = inFile.name; 397 Package.File bits = readFile(name, in); 398 Package.File file = null; 399 // (5078608) : discount the resource files in META-INF 400 // from segment computation. 401 long inflen = (inFile.isMetaInfFile()) 402 ? 0L 403 : inFile.getInputLength(); 404 405 if ((segmentSize += inflen) > segmentLimit) { 406 segmentSize -= inflen; 407 int nextCount = -1; // don't know; it's a stream 408 flushPartial(out, nextCount); 409 } 410 if (verbose > 1) { 411 Utils.log.fine("Reading " + name); 412 } 413 414 assert(je.isDirectory() == name.endsWith("/")); 415 416 if (inFile.mustProcess()) { 417 file = readClass(name, bits.getInputStream()); 418 } 419 if (file == null) { 420 file = bits; 421 pkg.addFile(file); 422 } 423 inFile.copyTo(file); 424 noteRead(inFile); 425 } 426 flushAll(out); 427 } 428 429 void run(JarFile in, OutputStream out) throws IOException { 430 List<InFile> inFiles = scanJar(in); 431 432 if (verbose > 0) 433 Utils.log.info("Reading " + inFiles.size() + " files..."); 434 435 int numDone = 0; 436 for (InFile inFile : inFiles) { 437 String name = inFile.name; 438 // (5078608) : discount the resource files completely from segmenting 439 long inflen = (inFile.isMetaInfFile()) 440 ? 0L 441 : inFile.getInputLength() ; 442 if ((segmentSize += inflen) > segmentLimit) { 443 segmentSize -= inflen; 444 // Estimate number of remaining segments: 445 float filesDone = numDone+1; 446 float segsDone = segmentCount+1; 447 float filesToDo = inFiles.size() - filesDone; 448 float segsToDo = filesToDo * (segsDone/filesDone); 449 if (verbose > 1) 450 Utils.log.fine("Estimated segments to do: "+segsToDo); 451 flushPartial(out, (int) Math.ceil(segsToDo)); 452 } 453 InputStream strm = inFile.getInputStream(); 454 if (verbose > 1) 455 Utils.log.fine("Reading " + name); 456 Package.File file = null; 457 if (inFile.mustProcess()) { 458 file = readClass(name, strm); 459 if (file == null) { 460 strm.close(); 461 strm = inFile.getInputStream(); 462 } 463 } 464 if (file == null) { 465 file = readFile(name, strm); 466 pkg.addFile(file); 467 } 468 inFile.copyTo(file); 469 strm.close(); // tidy up 470 noteRead(inFile); 471 numDone += 1; 472 } 473 flushAll(out); 474 } 475 476 Package.File readClass(String fname, InputStream in) throws IOException { 477 Package.Class cls = pkg.new Class(fname); 478 in = new BufferedInputStream(in); 479 ClassReader reader = new ClassReader(cls, in); 480 reader.setAttrDefs(attrDefs); 481 reader.setAttrCommands(attrCommands); 482 reader.unknownAttrCommand = unknownAttrCommand; 483 try { 484 reader.read(); 485 } catch (IOException ioe) { 486 String message = "Passing class file uncompressed due to"; 487 if (ioe instanceof Attribute.FormatException) { 488 Attribute.FormatException ee = (Attribute.FormatException) ioe; 489 // He passed up the category to us in layout. 490 if (ee.layout.equals(Pack200.Packer.PASS)) { 491 Utils.log.info(ee.toString()); 492 Utils.log.warning(message + " unrecognized attribute: " + 493 fname); 494 return null; 495 } 496 } else if (ioe instanceof ClassReader.ClassFormatException) { 497 ClassReader.ClassFormatException ce = (ClassReader.ClassFormatException) ioe; 498 if (classFormatCommand.equals(Pack200.Packer.PASS)) { 499 Utils.log.info(ce.toString()); 500 Utils.log.warning(message + " unknown class format: " + 501 fname); 502 return null; 503 } 504 } 505 // Otherwise, it must be an error. 506 throw ioe; 507 } 508 pkg.addClass(cls); 509 return cls.file; 510 } 511 512 // Read raw data. 513 Package.File readFile(String fname, InputStream in) throws IOException { 514 515 Package.File file = pkg.new File(fname); 516 file.readFrom(in); 517 if (file.isDirectory() && file.getFileLength() != 0) 518 throw new IllegalArgumentException("Non-empty directory: "+file.getFileName()); 519 return file; 520 } 521 522 void flushPartial(OutputStream out, int nextCount) throws IOException { 523 if (pkg.files.isEmpty() && pkg.classes.isEmpty()) { 524 return; // do not flush an empty segment 525 } 526 flushPackage(out, Math.max(1, nextCount)); 527 props.setInteger(Pack200.Packer.PROGRESS, 25); 528 // In case there will be another segment: 529 makeNextPackage(); 530 segmentCount += 1; 531 segmentTotalSize += segmentSize; 532 segmentSize = 0; 533 } 534 535 void flushAll(OutputStream out) throws IOException { 536 props.setInteger(Pack200.Packer.PROGRESS, 50); 537 flushPackage(out, 0); 538 out.flush(); 539 props.setInteger(Pack200.Packer.PROGRESS, 100); 540 segmentCount += 1; 541 segmentTotalSize += segmentSize; 542 segmentSize = 0; 543 if (verbose > 0 && segmentCount > 1) { 544 Utils.log.info("Transmitted " 545 +segmentTotalSize+" input bytes in " 546 +segmentCount+" segments totaling " 547 +totalOutputSize+" bytes"); 548 } 549 } 550 551 552 /** Write all information in the current package segment 553 * to the output stream. 554 */ 555 void flushPackage(OutputStream out, int nextCount) throws IOException { 556 int nfiles = pkg.files.size(); 557 if (!keepFileOrder) { 558 // Keeping the order of classes costs about 1% 559 // Keeping the order of all files costs something more. 560 if (verbose > 1) Utils.log.fine("Reordering files."); 561 boolean stripDirectories = true; 562 pkg.reorderFiles(keepClassOrder, stripDirectories); 563 } else { 564 // Package builder must have created a stub for each class. 565 assert(pkg.files.containsAll(pkg.getClassStubs())); 566 // Order of stubs in file list must agree with classes. 567 List<Package.File> res = pkg.files; 568 assert((res = new ArrayList<>(pkg.files)) 569 .retainAll(pkg.getClassStubs()) || true); 570 assert(res.equals(pkg.getClassStubs())); 571 } 572 pkg.trimStubs(); 573 574 // Do some stripping, maybe. 575 if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); 576 if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); 577 if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); 578 if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); 579 if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); 580 581 PackageWriter pw = new PackageWriter(pkg, out); 582 pw.archiveNextCount = nextCount; 583 pw.write(); 584 out.flush(); 585 if (verbose > 0) { 586 long outSize = pw.archiveSize0+pw.archiveSize1; 587 totalOutputSize += outSize; 588 long inSize = segmentSize; 589 Utils.log.info("Transmitted " 590 +nfiles+" files of " 591 +inSize+" input bytes in a segment of " 592 +outSize+" bytes"); 593 } 594 } 595 596 List<InFile> scanJar(JarFile jf) throws IOException { 597 // Collect jar entries, preserving order. 598 List<InFile> inFiles = new ArrayList<>(); 599 try { 600 for (JarEntry je : Collections.list(jf.entries())) { 601 InFile inFile = new InFile(jf, je); 602 assert(je.isDirectory() == inFile.name.endsWith("/")); 603 inFiles.add(inFile); 604 } 605 } catch (IllegalStateException ise) { 606 throw new IOException(ise.getLocalizedMessage(), ise); 607 } 608 return inFiles; 609 } 610 } 611 }