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 }