1 /* 2 * Copyright (c) 2003, 2013, 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 27 package com.sun.java.util.jar.pack; 28 29 import java.io.BufferedInputStream; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.nio.ByteBuffer; 35 import java.util.jar.JarOutputStream; 36 import java.util.jar.Pack200; 37 import java.util.zip.CRC32; 38 import java.util.zip.Deflater; 39 import java.util.zip.ZipEntry; 40 import java.util.zip.ZipOutputStream; 41 42 @SuppressWarnings({"removal"}) 43 class NativeUnpack { 44 // Pointer to the native unpacker obj 45 private long unpackerPtr; 46 47 // Input stream. 48 private BufferedInputStream in; 49 50 private static synchronized native void initIDs(); 51 52 // Starts processing at the indicated position in the buffer. 53 // If the buffer is null, the readInputFn callback is used to get bytes. 54 // Returns (s<<32|f), the number of following segments and files. 55 private synchronized native long start(ByteBuffer buf, long offset); 56 57 // Returns true if there's another, and fills in the parts. 58 private synchronized native boolean getNextFile(Object[] parts); 59 60 private synchronized native ByteBuffer getUnusedInput(); 61 62 // Resets the engine and frees all resources. 63 // Returns total number of bytes consumed by the engine. 64 private synchronized native long finish(); 65 66 // Setting state in the unpacker. 67 protected synchronized native boolean setOption(String opt, String value); 68 protected synchronized native String getOption(String opt); 69 70 private int _verbose; 71 72 // State for progress bar: 73 private long _byteCount; // bytes read in current segment 74 private int _segCount; // number of segs scanned 75 private int _fileCount; // number of files written 76 private long _estByteLimit; // estimate of eventual total 77 private int _estSegLimit; // ditto 78 private int _estFileLimit; // ditto 79 private int _prevPercent = -1; // for monotonicity 80 81 private final CRC32 _crc32 = new CRC32(); 82 private byte[] _buf = new byte[1<<14]; 83 84 private UnpackerImpl _p200; 85 private PropMap _props; 86 87 static { 88 // If loading from stand alone build uncomment this. 89 // System.loadLibrary("unpack"); 90 java.security.AccessController.doPrivileged( 91 new java.security.PrivilegedAction<>() { 92 public Void run() { 93 System.loadLibrary("unpack"); 94 return null; 95 } 96 }); 97 initIDs(); 98 } 99 100 NativeUnpack(UnpackerImpl p200) { 101 super(); 102 _p200 = p200; 103 _props = p200.props; 104 p200._nunp = this; 105 } 106 107 // for JNI callbacks 108 private static Object currentInstance() { 109 UnpackerImpl p200 = (UnpackerImpl) Utils.getTLGlobals(); 110 return (p200 == null)? null: p200._nunp; 111 } 112 113 private synchronized long getUnpackerPtr() { 114 return unpackerPtr; 115 } 116 117 // Callback from the unpacker engine to get more data. 118 private long readInputFn(ByteBuffer pbuf, long minlen) throws IOException { 119 if (in == null) return 0; // nothing is readable 120 long maxlen = pbuf.capacity() - pbuf.position(); 121 assert(minlen <= maxlen); // don't talk nonsense 122 long numread = 0; 123 int steps = 0; 124 while (numread < minlen) { 125 steps++; 126 // read available input, up to buf.length or maxlen 127 int readlen = _buf.length; 128 if (readlen > (maxlen - numread)) 129 readlen = (int)(maxlen - numread); 130 int nr = in.read(_buf, 0, readlen); 131 if (nr <= 0) break; 132 numread += nr; 133 assert(numread <= maxlen); 134 // %%% get rid of this extra copy by using nio? 135 pbuf.put(_buf, 0, nr); 136 } 137 if (_verbose > 1) 138 Utils.log.fine("readInputFn("+minlen+","+maxlen+") => "+numread+" steps="+steps); 139 if (maxlen > 100) { 140 _estByteLimit = _byteCount + maxlen; 141 } else { 142 _estByteLimit = (_byteCount + numread) * 20; 143 } 144 _byteCount += numread; 145 updateProgress(); 146 return numread; 147 } 148 149 private void updateProgress() { 150 // Progress is a combination of segment reading and file writing. 151 final double READ_WT = 0.33; 152 final double WRITE_WT = 0.67; 153 double readProgress = _segCount; 154 if (_estByteLimit > 0 && _byteCount > 0) 155 readProgress += (double)_byteCount / _estByteLimit; 156 double writeProgress = _fileCount; 157 double scaledProgress 158 = READ_WT * readProgress / Math.max(_estSegLimit,1) 159 + WRITE_WT * writeProgress / Math.max(_estFileLimit,1); 160 int percent = (int) Math.round(100*scaledProgress); 161 if (percent > 100) percent = 100; 162 if (percent > _prevPercent) { 163 _prevPercent = percent; 164 _props.setInteger(Pack200.Unpacker.PROGRESS, percent); 165 if (_verbose > 0) 166 Utils.log.info("progress = "+percent); 167 } 168 } 169 170 private void copyInOption(String opt) { 171 String val = _props.getProperty(opt); 172 if (_verbose > 0) 173 Utils.log.info("set "+opt+"="+val); 174 if (val != null) { 175 boolean set = setOption(opt, val); 176 if (!set) 177 Utils.log.warning("Invalid option "+opt+"="+val); 178 } 179 } 180 181 void run(InputStream inRaw, JarOutputStream jstream, 182 ByteBuffer presetInput) throws IOException { 183 BufferedInputStream in0 = new BufferedInputStream(inRaw); 184 this.in = in0; // for readInputFn to see 185 _verbose = _props.getInteger(Utils.DEBUG_VERBOSE); 186 // Fix for BugId: 4902477, -unpack.modification.time = 1059010598000 187 // TODO eliminate and fix in unpack.cpp 188 189 final int modtime = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, "0")) ? 190 Constants.NO_MODTIME : _props.getTime(Utils.UNPACK_MODIFICATION_TIME); 191 192 copyInOption(Utils.DEBUG_VERBOSE); 193 copyInOption(Pack200.Unpacker.DEFLATE_HINT); 194 if (modtime == Constants.NO_MODTIME) // Don't pass KEEP && NOW 195 copyInOption(Utils.UNPACK_MODIFICATION_TIME); 196 updateProgress(); // reset progress bar 197 for (;;) { 198 // Read the packed bits. 199 long counts = start(presetInput, 0); 200 _byteCount = _estByteLimit = 0; // reset partial scan counts 201 ++_segCount; // just finished scanning a whole segment... 202 int nextSeg = (int)( counts >>> 32 ); 203 int nextFile = (int)( counts >>> 0 ); 204 205 // Estimate eventual total number of segments and files. 206 _estSegLimit = _segCount + nextSeg; 207 double filesAfterThisSeg = _fileCount + nextFile; 208 _estFileLimit = (int)( (filesAfterThisSeg * 209 _estSegLimit) / _segCount ); 210 211 // Write the files. 212 int[] intParts = { 0,0, 0, 0 }; 213 // intParts = {size.hi/lo, mod, defl} 214 Object[] parts = { intParts, null, null, null }; 215 // parts = { {intParts}, name, data0/1 } 216 while (getNextFile(parts)) { 217 //BandStructure.printArrayTo(System.out, intParts, 0, parts.length); 218 String name = (String) parts[1]; 219 long size = ( (long)intParts[0] << 32) 220 + (((long)intParts[1] << 32) >>> 32); 221 222 long mtime = (modtime != Constants.NO_MODTIME ) ? 223 modtime : intParts[2] ; 224 boolean deflateHint = (intParts[3] != 0); 225 ByteBuffer data0 = (ByteBuffer) parts[2]; 226 ByteBuffer data1 = (ByteBuffer) parts[3]; 227 writeEntry(jstream, name, mtime, size, deflateHint, 228 data0, data1); 229 ++_fileCount; 230 updateProgress(); 231 } 232 presetInput = getUnusedInput(); 233 long consumed = finish(); 234 if (_verbose > 0) 235 Utils.log.info("bytes consumed = "+consumed); 236 if (presetInput == null && 237 !Utils.isPackMagic(Utils.readMagic(in0))) { 238 break; 239 } 240 if (_verbose > 0 ) { 241 if (presetInput != null) 242 Utils.log.info("unused input = "+presetInput); 243 } 244 } 245 } 246 247 void run(InputStream in, JarOutputStream jstream) throws IOException { 248 run(in, jstream, null); 249 } 250 251 void run(File inFile, JarOutputStream jstream) throws IOException { 252 // %%% maybe memory-map the file, and pass it straight into unpacker 253 ByteBuffer mappedFile = null; 254 try (FileInputStream fis = new FileInputStream(inFile)) { 255 run(fis, jstream, mappedFile); 256 } 257 // Note: caller is responsible to finish with jstream. 258 } 259 260 private void writeEntry(JarOutputStream j, String name, 261 long mtime, long lsize, boolean deflateHint, 262 ByteBuffer data0, ByteBuffer data1) throws IOException { 263 int size = (int)lsize; 264 if (size != lsize) 265 throw new IOException("file too large: "+lsize); 266 267 CRC32 crc32 = _crc32; 268 269 if (_verbose > 1) 270 Utils.log.fine("Writing entry: "+name+" size="+size 271 +(deflateHint?" deflated":"")); 272 273 if (_buf.length < size) { 274 int newSize = size; 275 while (newSize < _buf.length) { 276 newSize <<= 1; 277 if (newSize <= 0) { 278 newSize = size; 279 break; 280 } 281 } 282 _buf = new byte[newSize]; 283 } 284 assert(_buf.length >= size); 285 286 int fillp = 0; 287 if (data0 != null) { 288 int size0 = data0.capacity(); 289 data0.get(_buf, fillp, size0); 290 fillp += size0; 291 } 292 if (data1 != null) { 293 int size1 = data1.capacity(); 294 data1.get(_buf, fillp, size1); 295 fillp += size1; 296 } 297 while (fillp < size) { 298 // Fill in rest of data from the stream itself. 299 int nr = in.read(_buf, fillp, size - fillp); 300 if (nr <= 0) throw new IOException("EOF at end of archive"); 301 fillp += nr; 302 } 303 304 ZipEntry z = new ZipEntry(name); 305 z.setTime(mtime * 1000); 306 307 if (size == 0) { 308 z.setMethod(ZipOutputStream.STORED); 309 z.setSize(0); 310 z.setCrc(0); 311 z.setCompressedSize(0); 312 } else if (!deflateHint) { 313 z.setMethod(ZipOutputStream.STORED); 314 z.setSize(size); 315 z.setCompressedSize(size); 316 crc32.reset(); 317 crc32.update(_buf, 0, size); 318 z.setCrc(crc32.getValue()); 319 } else { 320 z.setMethod(Deflater.DEFLATED); 321 z.setSize(size); 322 } 323 324 j.putNextEntry(z); 325 326 if (size > 0) 327 j.write(_buf, 0, size); 328 329 j.closeEntry(); 330 if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z)); 331 } 332 }