DfsInserter.java

  1. /*
  2.  * Copyright (C) 2011, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.dfs;

  11. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  12. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  13. import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
  14. import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;

  15. import java.io.BufferedInputStream;
  16. import java.io.EOFException;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.OutputStream;
  20. import java.nio.ByteBuffer;
  21. import java.security.MessageDigest;
  22. import java.text.MessageFormat;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Set;
  28. import java.util.zip.CRC32;
  29. import java.util.zip.DataFormatException;
  30. import java.util.zip.Deflater;
  31. import java.util.zip.DeflaterOutputStream;
  32. import java.util.zip.Inflater;
  33. import java.util.zip.InflaterInputStream;

  34. import org.eclipse.jgit.annotations.Nullable;
  35. import org.eclipse.jgit.errors.CorruptObjectException;
  36. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  37. import org.eclipse.jgit.errors.LargeObjectException;
  38. import org.eclipse.jgit.internal.JGitText;
  39. import org.eclipse.jgit.internal.storage.file.PackIndex;
  40. import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
  41. import org.eclipse.jgit.internal.storage.pack.PackExt;
  42. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  43. import org.eclipse.jgit.lib.AnyObjectId;
  44. import org.eclipse.jgit.lib.Constants;
  45. import org.eclipse.jgit.lib.ObjectId;
  46. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  47. import org.eclipse.jgit.lib.ObjectInserter;
  48. import org.eclipse.jgit.lib.ObjectLoader;
  49. import org.eclipse.jgit.lib.ObjectReader;
  50. import org.eclipse.jgit.lib.ObjectStream;
  51. import org.eclipse.jgit.transport.PackedObjectInfo;
  52. import org.eclipse.jgit.util.BlockList;
  53. import org.eclipse.jgit.util.IO;
  54. import org.eclipse.jgit.util.NB;
  55. import org.eclipse.jgit.util.TemporaryBuffer;
  56. import org.eclipse.jgit.util.io.CountingOutputStream;
  57. import org.eclipse.jgit.util.sha1.SHA1;

  58. /**
  59.  * Inserts objects into the DFS.
  60.  */
  61. public class DfsInserter extends ObjectInserter {
  62.     /** Always produce version 2 indexes, to get CRC data. */
  63.     private static final int INDEX_VERSION = 2;

  64.     final DfsObjDatabase db;
  65.     int compression = Deflater.BEST_COMPRESSION;

  66.     List<PackedObjectInfo> objectList;
  67.     ObjectIdOwnerMap<PackedObjectInfo> objectMap;

  68.     DfsBlockCache cache;
  69.     DfsStreamKey packKey;
  70.     DfsPackDescription packDsc;
  71.     PackStream packOut;
  72.     private boolean rollback;
  73.     private boolean checkExisting = true;

  74.     /**
  75.      * Initialize a new inserter.
  76.      *
  77.      * @param db
  78.      *            database the inserter writes to.
  79.      */
  80.     protected DfsInserter(DfsObjDatabase db) {
  81.         this.db = db;
  82.     }

  83.     /**
  84.      * Check existence
  85.      *
  86.      * @param check
  87.      *            if {@code false}, will write out possibly-duplicate objects
  88.      *            without first checking whether they exist in the repo; default
  89.      *            is true.
  90.      */
  91.     public void checkExisting(boolean check) {
  92.         checkExisting = check;
  93.     }

  94.     void setCompressionLevel(int compression) {
  95.         this.compression = compression;
  96.     }

  97.     /** {@inheritDoc} */
  98.     @Override
  99.     public DfsPackParser newPackParser(InputStream in) throws IOException {
  100.         return new DfsPackParser(db, this, in);
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public ObjectReader newReader() {
  105.         return new Reader();
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public ObjectId insert(int type, byte[] data, int off, int len)
  110.             throws IOException {
  111.         ObjectId id = idFor(type, data, off, len);
  112.         if (objectMap != null && objectMap.contains(id))
  113.             return id;
  114.         // Ignore unreachable (garbage) objects here.
  115.         if (checkExisting && db.has(id, true))
  116.             return id;

  117.         long offset = beginObject(type, len);
  118.         packOut.compress.write(data, off, len);
  119.         packOut.compress.finish();
  120.         return endObject(id, offset);
  121.     }

  122.     /** {@inheritDoc} */
  123.     @Override
  124.     public ObjectId insert(int type, long len, InputStream in)
  125.             throws IOException {
  126.         byte[] buf = insertBuffer(len);
  127.         if (len <= buf.length) {
  128.             IO.readFully(in, buf, 0, (int) len);
  129.             return insert(type, buf, 0, (int) len);
  130.         }

  131.         long offset = beginObject(type, len);
  132.         SHA1 md = digest();
  133.         md.update(Constants.encodedTypeString(type));
  134.         md.update((byte) ' ');
  135.         md.update(Constants.encodeASCII(len));
  136.         md.update((byte) 0);

  137.         while (0 < len) {
  138.             int n = in.read(buf, 0, (int) Math.min(buf.length, len));
  139.             if (n <= 0)
  140.                 throw new EOFException();
  141.             md.update(buf, 0, n);
  142.             packOut.compress.write(buf, 0, n);
  143.             len -= n;
  144.         }
  145.         packOut.compress.finish();
  146.         return endObject(md.toObjectId(), offset);
  147.     }

  148.     private byte[] insertBuffer(long len) {
  149.         byte[] buf = buffer();
  150.         if (len <= buf.length)
  151.             return buf;
  152.         if (len < db.getReaderOptions().getStreamFileThreshold()) {
  153.             try {
  154.                 return new byte[(int) len];
  155.             } catch (OutOfMemoryError noMem) {
  156.                 return buf;
  157.             }
  158.         }
  159.         return buf;
  160.     }

  161.     /** {@inheritDoc} */
  162.     @Override
  163.     public void flush() throws IOException {
  164.         if (packDsc == null)
  165.             return;

  166.         if (packOut == null)
  167.             throw new IOException();

  168.         byte[] packHash = packOut.writePackFooter();
  169.         packDsc.addFileExt(PACK);
  170.         packDsc.setFileSize(PACK, packOut.getCount());
  171.         packOut.close();
  172.         packOut = null;

  173.         sortObjectsById();

  174.         PackIndex index = writePackIndex(packDsc, packHash, objectList);
  175.         db.commitPack(Collections.singletonList(packDsc), null);
  176.         rollback = false;

  177.         DfsPackFile p = new DfsPackFile(cache, packDsc);
  178.         if (index != null)
  179.             p.setPackIndex(index);
  180.         db.addPack(p);
  181.         clear();
  182.     }

  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public void close() {
  186.         if (packOut != null) {
  187.             try {
  188.                 packOut.close();
  189.             } catch (IOException err) {
  190.                 // Ignore a close failure, the pack should be removed.
  191.             } finally {
  192.                 packOut = null;
  193.             }
  194.         }
  195.         if (rollback && packDsc != null) {
  196.             try {
  197.                 db.rollbackPack(Collections.singletonList(packDsc));
  198.             } finally {
  199.                 packDsc = null;
  200.                 rollback = false;
  201.             }
  202.         }
  203.         clear();
  204.     }

  205.     private void clear() {
  206.         objectList = null;
  207.         objectMap = null;
  208.         packKey = null;
  209.         packDsc = null;
  210.     }

  211.     private long beginObject(int type, long len) throws IOException {
  212.         if (packOut == null)
  213.             beginPack();
  214.         long offset = packOut.getCount();
  215.         packOut.beginObject(type, len);
  216.         return offset;
  217.     }

  218.     private ObjectId endObject(ObjectId id, long offset) {
  219.         PackedObjectInfo obj = new PackedObjectInfo(id);
  220.         obj.setOffset(offset);
  221.         obj.setCRC((int) packOut.crc32.getValue());
  222.         objectList.add(obj);
  223.         objectMap.addIfAbsent(obj);
  224.         return id;
  225.     }

  226.     private void beginPack() throws IOException {
  227.         objectList = new BlockList<>();
  228.         objectMap = new ObjectIdOwnerMap<>();
  229.         cache = DfsBlockCache.getInstance();

  230.         rollback = true;
  231.         packDsc = db.newPack(DfsObjDatabase.PackSource.INSERT);
  232.         DfsOutputStream dfsOut = db.writeFile(packDsc, PACK);
  233.         packDsc.setBlockSize(PACK, dfsOut.blockSize());
  234.         packOut = new PackStream(dfsOut);
  235.         packKey = packDsc.getStreamKey(PACK);

  236.         // Write the header as though it were a single object pack.
  237.         byte[] buf = packOut.hdrBuf;
  238.         System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4);
  239.         NB.encodeInt32(buf, 4, 2); // Always use pack version 2.
  240.         NB.encodeInt32(buf, 8, 1); // Always assume 1 object.
  241.         packOut.write(buf, 0, 12);
  242.     }

  243.     private void sortObjectsById() {
  244.         Collections.sort(objectList);
  245.     }

  246.     @Nullable
  247.     private TemporaryBuffer.Heap maybeGetTemporaryBuffer(
  248.             List<PackedObjectInfo> list) {
  249.         if (list.size() <= 58000) {
  250.             return new TemporaryBuffer.Heap(2 << 20);
  251.         }
  252.         return null;
  253.     }

  254.     PackIndex writePackIndex(DfsPackDescription pack, byte[] packHash,
  255.             List<PackedObjectInfo> list) throws IOException {
  256.         pack.setIndexVersion(INDEX_VERSION);
  257.         pack.setObjectCount(list.size());

  258.         // If there are less than 58,000 objects, the entire index fits in under
  259.         // 2 MiB. Callers will probably need the index immediately, so buffer
  260.         // the index in process and load from the buffer.
  261.         PackIndex packIndex = null;
  262.         try (TemporaryBuffer.Heap buf = maybeGetTemporaryBuffer(list);
  263.                 DfsOutputStream os = db.writeFile(pack, INDEX);
  264.                 CountingOutputStream cnt = new CountingOutputStream(os)) {
  265.             if (buf != null) {
  266.                 index(buf, packHash, list);
  267.                 packIndex = PackIndex.read(buf.openInputStream());
  268.                 buf.writeTo(cnt, null);
  269.             } else {
  270.                 index(cnt, packHash, list);
  271.             }
  272.             pack.addFileExt(INDEX);
  273.             pack.setBlockSize(INDEX, os.blockSize());
  274.             pack.setFileSize(INDEX, cnt.getCount());
  275.         }
  276.         return packIndex;
  277.     }

  278.     private static void index(OutputStream out, byte[] packHash,
  279.             List<PackedObjectInfo> list) throws IOException {
  280.         PackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
  281.     }

  282.     private class PackStream extends OutputStream {
  283.         private final DfsOutputStream out;
  284.         private final MessageDigest md;
  285.         final byte[] hdrBuf;
  286.         private final Deflater deflater;
  287.         private final int blockSize;

  288.         private long currPos; // Position of currBuf[0] in the output stream.
  289.         private int currPtr; // Number of bytes in currBuf.
  290.         private byte[] currBuf;

  291.         final CRC32 crc32;
  292.         final DeflaterOutputStream compress;

  293.         PackStream(DfsOutputStream out) {
  294.             this.out = out;

  295.             hdrBuf = new byte[32];
  296.             md = Constants.newMessageDigest();
  297.             crc32 = new CRC32();
  298.             deflater = new Deflater(compression);
  299.             compress = new DeflaterOutputStream(this, deflater, 8192);

  300.             int size = out.blockSize();
  301.             if (size <= 0)
  302.                 size = cache.getBlockSize();
  303.             else if (size < cache.getBlockSize())
  304.                 size = (cache.getBlockSize() / size) * size;
  305.             blockSize = size;
  306.             currBuf = new byte[blockSize];
  307.         }

  308.         long getCount() {
  309.             return currPos + currPtr;
  310.         }

  311.         void beginObject(int objectType, long length) throws IOException {
  312.             crc32.reset();
  313.             deflater.reset();
  314.             write(hdrBuf, 0, encodeTypeSize(objectType, length));
  315.         }

  316.         private int encodeTypeSize(int type, long rawLength) {
  317.             long nextLength = rawLength >>> 4;
  318.             hdrBuf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
  319.             rawLength = nextLength;
  320.             int n = 1;
  321.             while (rawLength > 0) {
  322.                 nextLength >>>= 7;
  323.                 hdrBuf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
  324.                 rawLength = nextLength;
  325.             }
  326.             return n;
  327.         }

  328.         @Override
  329.         public void write(int b) throws IOException {
  330.             hdrBuf[0] = (byte) b;
  331.             write(hdrBuf, 0, 1);
  332.         }

  333.         @Override
  334.         public void write(byte[] data, int off, int len) throws IOException {
  335.             crc32.update(data, off, len);
  336.             md.update(data, off, len);
  337.             writeNoHash(data, off, len);
  338.         }

  339.         private void writeNoHash(byte[] data, int off, int len)
  340.                 throws IOException {
  341.             while (0 < len) {
  342.                 int n = Math.min(len, currBuf.length - currPtr);
  343.                 if (n == 0) {
  344.                     flushBlock();
  345.                     currBuf = new byte[blockSize];
  346.                     continue;
  347.                 }

  348.                 System.arraycopy(data, off, currBuf, currPtr, n);
  349.                 off += n;
  350.                 len -= n;
  351.                 currPtr += n;
  352.             }
  353.         }

  354.         private void flushBlock() throws IOException {
  355.             out.write(currBuf, 0, currPtr);

  356.             byte[] buf;
  357.             if (currPtr == currBuf.length)
  358.                 buf = currBuf;
  359.             else
  360.                 buf = copyOf(currBuf, 0, currPtr);
  361.             cache.put(new DfsBlock(packKey, currPos, buf));

  362.             currPos += currPtr;
  363.             currPtr = 0;
  364.             currBuf = null;
  365.         }

  366.         private byte[] copyOf(byte[] src, int ptr, int cnt) {
  367.             byte[] dst = new byte[cnt];
  368.             System.arraycopy(src, ptr, dst, 0, cnt);
  369.             return dst;
  370.         }

  371.         byte[] writePackFooter() throws IOException {
  372.             byte[] packHash = md.digest();
  373.             writeNoHash(packHash, 0, packHash.length);
  374.             if (currPtr != 0)
  375.                 flushBlock();
  376.             return packHash;
  377.         }

  378.         int read(long pos, byte[] dst, int ptr, int cnt) throws IOException {
  379.             int r = 0;
  380.             while (pos < currPos && r < cnt) {
  381.                 DfsBlock b = getOrLoadBlock(pos);
  382.                 int n = b.copy(pos, dst, ptr + r, cnt - r);
  383.                 pos += n;
  384.                 r += n;
  385.             }
  386.             if (currPos <= pos && r < cnt) {
  387.                 int s = (int) (pos - currPos);
  388.                 int n = Math.min(currPtr - s, cnt - r);
  389.                 System.arraycopy(currBuf, s, dst, ptr + r, n);
  390.                 r += n;
  391.             }
  392.             return r;
  393.         }

  394.         byte[] inflate(DfsReader ctx, long pos, int len) throws IOException,
  395.                 DataFormatException {
  396.             byte[] dstbuf;
  397.             try {
  398.                 dstbuf = new byte[len];
  399.             } catch (OutOfMemoryError noMemory) {
  400.                 return null; // Caller will switch to large object streaming.
  401.             }

  402.             Inflater inf = ctx.inflater();
  403.             pos += setInput(pos, inf);
  404.             for (int dstoff = 0;;) {
  405.                 int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
  406.                 dstoff += n;
  407.                 if (inf.finished())
  408.                     return dstbuf;
  409.                 if (inf.needsInput())
  410.                     pos += setInput(pos, inf);
  411.                 else if (n == 0)
  412.                     throw new DataFormatException();
  413.             }
  414.         }

  415.         private int setInput(long pos, Inflater inf)
  416.                 throws IOException, DataFormatException {
  417.             if (pos < currPos)
  418.                 return getOrLoadBlock(pos).setInput(pos, inf);
  419.             if (pos < currPos + currPtr) {
  420.                 int s = (int) (pos - currPos);
  421.                 int n = currPtr - s;
  422.                 inf.setInput(currBuf, s, n);
  423.                 return n;
  424.             }
  425.             throw new EOFException(JGitText.get().unexpectedEofInPack);
  426.         }

  427.         private DfsBlock getOrLoadBlock(long pos) throws IOException {
  428.             long s = toBlockStart(pos);
  429.             DfsBlock b = cache.get(packKey, s);
  430.             if (b != null)
  431.                 return b;

  432.             byte[] d = new byte[blockSize];
  433.             for (int p = 0; p < blockSize;) {
  434.                 int n = out.read(s + p, ByteBuffer.wrap(d, p, blockSize - p));
  435.                 if (n <= 0)
  436.                     throw new EOFException(JGitText.get().unexpectedEofInPack);
  437.                 p += n;
  438.             }
  439.             b = new DfsBlock(packKey, s, d);
  440.             cache.put(b);
  441.             return b;
  442.         }

  443.         private long toBlockStart(long pos) {
  444.             return (pos / blockSize) * blockSize;
  445.         }

  446.         @Override
  447.         public void close() throws IOException {
  448.             deflater.end();
  449.             out.close();
  450.         }
  451.     }

  452.     private class Reader extends ObjectReader {
  453.         private final DfsReader ctx = db.newReader();

  454.         @Override
  455.         public ObjectReader newReader() {
  456.             return db.newReader();
  457.         }

  458.         @Override
  459.         public Collection<ObjectId> resolve(AbbreviatedObjectId id)
  460.                 throws IOException {
  461.             Collection<ObjectId> stored = ctx.resolve(id);
  462.             if (objectList == null)
  463.                 return stored;

  464.             Set<ObjectId> r = new HashSet<>(stored.size() + 2);
  465.             r.addAll(stored);
  466.             for (PackedObjectInfo obj : objectList) {
  467.                 if (id.prefixCompare(obj) == 0)
  468.                     r.add(obj.copy());
  469.             }
  470.             return r;
  471.         }

  472.         @Override
  473.         public ObjectLoader open(AnyObjectId objectId, int typeHint)
  474.                 throws IOException {
  475.             if (objectMap == null)
  476.                 return ctx.open(objectId, typeHint);

  477.             PackedObjectInfo obj = objectMap.get(objectId);
  478.             if (obj == null)
  479.                 return ctx.open(objectId, typeHint);

  480.             byte[] buf = buffer();
  481.             int cnt = packOut.read(obj.getOffset(), buf, 0, 20);
  482.             if (cnt <= 0)
  483.                     throw new EOFException(JGitText.get().unexpectedEofInPack);

  484.             int c = buf[0] & 0xff;
  485.             int type = (c >> 4) & 7;
  486.             if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA)
  487.                 throw new IOException(MessageFormat.format(
  488.                         JGitText.get().cannotReadBackDelta, Integer.toString(type)));
  489.             if (typeHint != OBJ_ANY && type != typeHint) {
  490.                 throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
  491.             }

  492.             long sz = c & 0x0f;
  493.             int ptr = 1;
  494.             int shift = 4;
  495.             while ((c & 0x80) != 0) {
  496.                 if (ptr >= cnt)
  497.                     throw new EOFException(JGitText.get().unexpectedEofInPack);
  498.                 c = buf[ptr++] & 0xff;
  499.                 sz += ((long) (c & 0x7f)) << shift;
  500.                 shift += 7;
  501.             }

  502.             long zpos = obj.getOffset() + ptr;
  503.             if (sz < ctx.getStreamFileThreshold()) {
  504.                 byte[] data = inflate(obj, zpos, (int) sz);
  505.                 if (data != null)
  506.                     return new ObjectLoader.SmallObject(type, data);
  507.             }
  508.             return new StreamLoader(obj.copy(), type, sz, packKey, zpos);
  509.         }

  510.         private byte[] inflate(PackedObjectInfo obj, long zpos, int sz)
  511.                 throws IOException, CorruptObjectException {
  512.             try {
  513.                 return packOut.inflate(ctx, zpos, sz);
  514.             } catch (DataFormatException dfe) {
  515.                 throw new CorruptObjectException(
  516.                         MessageFormat.format(
  517.                                 JGitText.get().objectAtHasBadZlibStream,
  518.                                 Long.valueOf(obj.getOffset()),
  519.                                 packDsc.getFileName(PackExt.PACK)),
  520.                         dfe);
  521.             }
  522.         }

  523.         @Override
  524.         public boolean has(AnyObjectId objectId) throws IOException {
  525.             return (objectMap != null && objectMap.contains(objectId))
  526.                     || ctx.has(objectId);
  527.         }

  528.         @Override
  529.         public Set<ObjectId> getShallowCommits() throws IOException {
  530.             return ctx.getShallowCommits();
  531.         }

  532.         @Override
  533.         public ObjectInserter getCreatedFromInserter() {
  534.             return DfsInserter.this;
  535.         }

  536.         @Override
  537.         public void close() {
  538.             ctx.close();
  539.         }
  540.     }

  541.     private class StreamLoader extends ObjectLoader {
  542.         private final ObjectId id;
  543.         private final int type;
  544.         private final long size;

  545.         private final DfsStreamKey srcPack;
  546.         private final long pos;

  547.         StreamLoader(ObjectId id, int type, long sz,
  548.                 DfsStreamKey key, long pos) {
  549.             this.id = id;
  550.             this.type = type;
  551.             this.size = sz;
  552.             this.srcPack = key;
  553.             this.pos = pos;
  554.         }

  555.         @Override
  556.         public ObjectStream openStream() throws IOException {
  557.             @SuppressWarnings("resource") // Explicitly closed below
  558.             final DfsReader ctx = db.newReader();
  559.             if (srcPack != packKey) {
  560.                 try {
  561.                     // Post DfsInserter.flush() use the normal code path.
  562.                     // The newly created pack is registered in the cache.
  563.                     return ctx.open(id, type).openStream();
  564.                 } finally {
  565.                     ctx.close();
  566.                 }
  567.             }

  568.             int bufsz = 8192;
  569.             final Inflater inf = ctx.inflater();
  570.             return new ObjectStream.Filter(type,
  571.                     size, new BufferedInputStream(new InflaterInputStream(
  572.                             new ReadBackStream(pos), inf, bufsz), bufsz)) {
  573.                 @Override
  574.                 public void close() throws IOException {
  575.                     ctx.close();
  576.                     super.close();
  577.                 }
  578.             };
  579.         }

  580.         @Override
  581.         public int getType() {
  582.             return type;
  583.         }

  584.         @Override
  585.         public long getSize() {
  586.             return size;
  587.         }

  588.         @Override
  589.         public boolean isLarge() {
  590.             return true;
  591.         }

  592.         @Override
  593.         public byte[] getCachedBytes() throws LargeObjectException {
  594.             throw new LargeObjectException.ExceedsLimit(
  595.                     db.getReaderOptions().getStreamFileThreshold(), size);
  596.         }
  597.     }

  598.     private final class ReadBackStream extends InputStream {
  599.         private long pos;

  600.         ReadBackStream(long offset) {
  601.             pos = offset;
  602.         }

  603.         @Override
  604.         public int read() throws IOException {
  605.             byte[] b = new byte[1];
  606.             int n = read(b);
  607.             return n == 1 ? b[0] & 0xff : -1;
  608.         }

  609.         @Override
  610.         public int read(byte[] buf, int ptr, int len) throws IOException {
  611.             int n = packOut.read(pos, buf, ptr, len);
  612.             if (n > 0) {
  613.                 pos += n;
  614.             }
  615.             return n;
  616.         }
  617.     }
  618. }