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