ObjectDirectoryPackParser.java

  1. /*
  2.  * Copyright (C) 2008-2011, Google Inc.
  3.  * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  4.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.internal.storage.file;

  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.RandomAccessFile;
  18. import java.nio.file.StandardCopyOption;
  19. import java.security.MessageDigest;
  20. import java.text.MessageFormat;
  21. import java.util.Arrays;
  22. import java.util.List;
  23. import java.util.zip.CRC32;
  24. import java.util.zip.Deflater;

  25. import org.eclipse.jgit.errors.LockFailedException;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.internal.storage.pack.PackExt;
  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.CoreConfig;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.ProgressMonitor;
  33. import org.eclipse.jgit.storage.pack.PackConfig;
  34. import org.eclipse.jgit.transport.PackLock;
  35. import org.eclipse.jgit.transport.PackParser;
  36. import org.eclipse.jgit.transport.PackedObjectInfo;
  37. import org.eclipse.jgit.util.FileUtils;
  38. import org.eclipse.jgit.util.NB;

  39. /**
  40.  * Consumes a pack stream and stores as a pack file in
  41.  * {@link org.eclipse.jgit.internal.storage.file.ObjectDirectory}.
  42.  * <p>
  43.  * To obtain an instance of a parser, applications should use
  44.  * {@link org.eclipse.jgit.lib.ObjectInserter#newPackParser(InputStream)}.
  45.  */
  46. public class ObjectDirectoryPackParser extends PackParser {
  47.     private final FileObjectDatabase db;

  48.     /** CRC-32 computation for objects that are appended onto the pack. */
  49.     private final CRC32 crc;

  50.     /** Running SHA-1 of any base objects appended after {@link #origEnd}. */
  51.     private final MessageDigest tailDigest;

  52.     /** Preferred format version of the pack-*.idx file to generate. */
  53.     private int indexVersion;

  54.     /** If true, pack with 0 objects will be stored. Usually these are deleted. */
  55.     private boolean keepEmpty;

  56.     /** Path of the temporary file holding the pack data. */
  57.     private File tmpPack;

  58.     /**
  59.      * Path of the index created for the pack, to find objects quickly at read
  60.      * time.
  61.      */
  62.     private File tmpIdx;

  63.     /** Read/write handle to {@link #tmpPack} while it is being parsed. */
  64.     private RandomAccessFile out;

  65.     /** Length of the original pack stream, before missing bases were appended. */
  66.     private long origEnd;

  67.     /** The original checksum of data up to {@link #origEnd}. */
  68.     private byte[] origHash;

  69.     /** Current end of the pack file. */
  70.     private long packEnd;

  71.     /** Checksum of the entire pack file. */
  72.     private byte[] packHash;

  73.     /** Compresses delta bases when completing a thin pack. */
  74.     private Deflater def;

  75.     /** The pack that was created, if parsing was successful. */
  76.     private Pack newPack;

  77.     private PackConfig pconfig;

  78.     ObjectDirectoryPackParser(FileObjectDatabase odb, InputStream src) {
  79.         super(odb, src);
  80.         this.db = odb;
  81.         this.pconfig = new PackConfig(odb.getConfig());
  82.         this.crc = new CRC32();
  83.         this.tailDigest = Constants.newMessageDigest();

  84.         indexVersion = db.getConfig().get(CoreConfig.KEY).getPackIndexVersion();
  85.     }

  86.     /**
  87.      * Set the pack index file format version this instance will create.
  88.      *
  89.      * @param version
  90.      *            the version to write. The special version 0 designates the
  91.      *            oldest (most compatible) format available for the objects.
  92.      * @see PackIndexWriter
  93.      */
  94.     public void setIndexVersion(int version) {
  95.         indexVersion = version;
  96.     }

  97.     /**
  98.      * Configure this index pack instance to keep an empty pack.
  99.      * <p>
  100.      * By default an empty pack (a pack with no objects) is not kept, as doi so
  101.      * is completely pointless. With no objects in the pack there is no d stored
  102.      * by it, so the pack is unnecessary.
  103.      *
  104.      * @param empty
  105.      *            true to enable keeping an empty pack.
  106.      */
  107.     public void setKeepEmpty(boolean empty) {
  108.         keepEmpty = empty;
  109.     }

  110.     /**
  111.      * Get the imported {@link org.eclipse.jgit.internal.storage.file.Pack}.
  112.      * <p>
  113.      * This method is supplied only to support testing; applications shouldn't
  114.      * be using it directly to access the imported data.
  115.      *
  116.      * @return the imported PackFile, if parsing was successful.
  117.      */
  118.     public Pack getPack() {
  119.         return newPack;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public long getPackSize() {
  124.         if (newPack == null)
  125.             return super.getPackSize();

  126.         File pack = newPack.getPackFile();
  127.         long size = pack.length();
  128.         String p = pack.getAbsolutePath();
  129.         String i = p.substring(0, p.length() - ".pack".length()) + ".idx"; //$NON-NLS-1$ //$NON-NLS-2$
  130.         File idx = new File(i);
  131.         if (idx.isFile())
  132.             size += idx.length();
  133.         return size;
  134.     }

  135.     /** {@inheritDoc} */
  136.     @Override
  137.     public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
  138.             throws IOException {
  139.         tmpPack = File.createTempFile("incoming_", ".pack", db.getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
  140.         tmpIdx = new File(db.getDirectory(), baseName(tmpPack) + ".idx"); //$NON-NLS-1$
  141.         try {
  142.             out = new RandomAccessFile(tmpPack, "rw"); //$NON-NLS-1$

  143.             super.parse(receiving, resolving);

  144.             out.seek(packEnd);
  145.             out.write(packHash);
  146.             out.getChannel().force(true);
  147.             out.close();

  148.             writeIdx();

  149.             tmpPack.setReadOnly();
  150.             tmpIdx.setReadOnly();

  151.             return renameAndOpenPack(getLockMessage());
  152.         } finally {
  153.             if (def != null)
  154.                 def.end();
  155.             try {
  156.                 if (out != null && out.getChannel().isOpen())
  157.                     out.close();
  158.             } catch (IOException closeError) {
  159.                 // Ignored. We want to delete the file.
  160.             }
  161.             cleanupTemporaryFiles();
  162.         }
  163.     }

  164.     /** {@inheritDoc} */
  165.     @Override
  166.     protected void onPackHeader(long objectCount) throws IOException {
  167.         // Ignored, the count is not required.
  168.     }

  169.     /** {@inheritDoc} */
  170.     @Override
  171.     protected void onBeginWholeObject(long streamPosition, int type,
  172.             long inflatedSize) throws IOException {
  173.         crc.reset();
  174.     }

  175.     /** {@inheritDoc} */
  176.     @Override
  177.     protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
  178.         info.setCRC((int) crc.getValue());
  179.     }

  180.     /** {@inheritDoc} */
  181.     @Override
  182.     protected void onBeginOfsDelta(long streamPosition,
  183.             long baseStreamPosition, long inflatedSize) throws IOException {
  184.         crc.reset();
  185.     }

  186.     /** {@inheritDoc} */
  187.     @Override
  188.     protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
  189.             long inflatedSize) throws IOException {
  190.         crc.reset();
  191.     }

  192.     /** {@inheritDoc} */
  193.     @Override
  194.     protected UnresolvedDelta onEndDelta() throws IOException {
  195.         UnresolvedDelta delta = new UnresolvedDelta();
  196.         delta.setCRC((int) crc.getValue());
  197.         return delta;
  198.     }

  199.     /** {@inheritDoc} */
  200.     @Override
  201.     protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
  202.             byte[] data) throws IOException {
  203.         // ObjectDirectory ignores this event.
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
  208.             throws IOException {
  209.         crc.update(raw, pos, len);
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     protected void onObjectData(Source src, byte[] raw, int pos, int len)
  214.             throws IOException {
  215.         crc.update(raw, pos, len);
  216.     }

  217.     /** {@inheritDoc} */
  218.     @Override
  219.     protected void onStoreStream(byte[] raw, int pos, int len)
  220.             throws IOException {
  221.         out.write(raw, pos, len);
  222.     }

  223.     /** {@inheritDoc} */
  224.     @Override
  225.     protected void onPackFooter(byte[] hash) throws IOException {
  226.         packEnd = out.getFilePointer();
  227.         origEnd = packEnd;
  228.         origHash = hash;
  229.         packHash = hash;
  230.     }

  231.     /** {@inheritDoc} */
  232.     @Override
  233.     protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
  234.             ObjectTypeAndSize info) throws IOException {
  235.         out.seek(delta.getOffset());
  236.         crc.reset();
  237.         return readObjectHeader(info);
  238.     }

  239.     /** {@inheritDoc} */
  240.     @Override
  241.     protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
  242.             ObjectTypeAndSize info) throws IOException {
  243.         out.seek(obj.getOffset());
  244.         crc.reset();
  245.         return readObjectHeader(info);
  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
  250.         return out.read(dst, pos, cnt);
  251.     }

  252.     /** {@inheritDoc} */
  253.     @Override
  254.     protected boolean checkCRC(int oldCRC) {
  255.         return oldCRC == (int) crc.getValue();
  256.     }

  257.     private static String baseName(File tmpPack) {
  258.         String name = tmpPack.getName();
  259.         return name.substring(0, name.lastIndexOf('.'));
  260.     }

  261.     private void cleanupTemporaryFiles() {
  262.         if (tmpIdx != null && !tmpIdx.delete() && tmpIdx.exists())
  263.             tmpIdx.deleteOnExit();
  264.         if (tmpPack != null && !tmpPack.delete() && tmpPack.exists())
  265.             tmpPack.deleteOnExit();
  266.     }

  267.     /** {@inheritDoc} */
  268.     @Override
  269.     protected boolean onAppendBase(final int typeCode, final byte[] data,
  270.             final PackedObjectInfo info) throws IOException {
  271.         info.setOffset(packEnd);

  272.         final byte[] buf = buffer();
  273.         int sz = data.length;
  274.         int len = 0;
  275.         buf[len++] = (byte) ((typeCode << 4) | (sz & 15));
  276.         sz >>>= 4;
  277.         while (sz > 0) {
  278.             buf[len - 1] |= (byte) 0x80;
  279.             buf[len++] = (byte) (sz & 0x7f);
  280.             sz >>>= 7;
  281.         }

  282.         tailDigest.update(buf, 0, len);
  283.         crc.reset();
  284.         crc.update(buf, 0, len);
  285.         out.seek(packEnd);
  286.         out.write(buf, 0, len);
  287.         packEnd += len;

  288.         if (def == null)
  289.             def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
  290.         else
  291.             def.reset();
  292.         def.setInput(data);
  293.         def.finish();

  294.         while (!def.finished()) {
  295.             len = def.deflate(buf);
  296.             tailDigest.update(buf, 0, len);
  297.             crc.update(buf, 0, len);
  298.             out.write(buf, 0, len);
  299.             packEnd += len;
  300.         }

  301.         info.setCRC((int) crc.getValue());
  302.         return true;
  303.     }

  304.     /** {@inheritDoc} */
  305.     @Override
  306.     protected void onEndThinPack() throws IOException {
  307.         final byte[] buf = buffer();

  308.         final MessageDigest origDigest = Constants.newMessageDigest();
  309.         final MessageDigest tailDigest2 = Constants.newMessageDigest();
  310.         final MessageDigest packDigest = Constants.newMessageDigest();

  311.         long origRemaining = origEnd;
  312.         out.seek(0);
  313.         out.readFully(buf, 0, 12);
  314.         origDigest.update(buf, 0, 12);
  315.         origRemaining -= 12;

  316.         NB.encodeInt32(buf, 8, getObjectCount());
  317.         out.seek(0);
  318.         out.write(buf, 0, 12);
  319.         packDigest.update(buf, 0, 12);

  320.         for (;;) {
  321.             final int n = out.read(buf);
  322.             if (n < 0)
  323.                 break;
  324.             if (origRemaining != 0) {
  325.                 final int origCnt = (int) Math.min(n, origRemaining);
  326.                 origDigest.update(buf, 0, origCnt);
  327.                 origRemaining -= origCnt;
  328.                 if (origRemaining == 0)
  329.                     tailDigest2.update(buf, origCnt, n - origCnt);
  330.             } else
  331.                 tailDigest2.update(buf, 0, n);

  332.             packDigest.update(buf, 0, n);
  333.         }

  334.         if (!Arrays.equals(origDigest.digest(), origHash) || !Arrays
  335.                 .equals(tailDigest2.digest(), this.tailDigest.digest()))
  336.             throw new IOException(
  337.                     JGitText.get().packCorruptedWhileWritingToFilesystem);

  338.         packHash = packDigest.digest();
  339.     }

  340.     private void writeIdx() throws IOException {
  341.         List<PackedObjectInfo> list = getSortedObjectList(null /* by ObjectId */);
  342.         try (FileOutputStream os = new FileOutputStream(tmpIdx)) {
  343.             final PackIndexWriter iw;
  344.             if (indexVersion <= 0)
  345.                 iw = PackIndexWriter.createOldestPossible(os, list);
  346.             else
  347.                 iw = PackIndexWriter.createVersion(os, indexVersion);
  348.             iw.write(list, packHash);
  349.             os.getChannel().force(true);
  350.         }
  351.     }

  352.     private PackLock renameAndOpenPack(String lockMessage)
  353.             throws IOException {
  354.         if (!keepEmpty && getObjectCount() == 0) {
  355.             cleanupTemporaryFiles();
  356.             return null;
  357.         }

  358.         final MessageDigest d = Constants.newMessageDigest();
  359.         final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
  360.         for (int i = 0; i < getObjectCount(); i++) {
  361.             final PackedObjectInfo oe = getObject(i);
  362.             oe.copyRawTo(oeBytes, 0);
  363.             d.update(oeBytes);
  364.         }

  365.         ObjectId id = ObjectId.fromRaw(d.digest());
  366.         File packDir = new File(db.getDirectory(), "pack"); //$NON-NLS-1$
  367.         PackFile finalPack = new PackFile(packDir, id, PackExt.PACK);
  368.         PackFile finalIdx = finalPack.create(PackExt.INDEX);
  369.         final PackLockImpl keep = new PackLockImpl(finalPack, db.getFS());

  370.         if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
  371.             // The objects/pack directory isn't present, and we are unable
  372.             // to create it. There is no way to move this pack in.
  373.             //
  374.             cleanupTemporaryFiles();
  375.             throw new IOException(MessageFormat.format(
  376.                     JGitText.get().cannotCreateDirectory, packDir
  377.                             .getAbsolutePath()));
  378.         }

  379.         if (finalPack.exists()) {
  380.             // If the pack is already present we should never replace it.
  381.             //
  382.             cleanupTemporaryFiles();
  383.             return null;
  384.         }

  385.         if (lockMessage != null) {
  386.             // If we have a reason to create a keep file for this pack, do
  387.             // so, or fail fast and don't put the pack in place.
  388.             //
  389.             try {
  390.                 if (!keep.lock(lockMessage))
  391.                     throw new LockFailedException(finalPack,
  392.                             MessageFormat.format(
  393.                                     JGitText.get().cannotLockPackIn, finalPack));
  394.             } catch (IOException e) {
  395.                 cleanupTemporaryFiles();
  396.                 throw e;
  397.             }
  398.         }

  399.         try {
  400.             FileUtils.rename(tmpPack, finalPack,
  401.                     StandardCopyOption.ATOMIC_MOVE);
  402.         } catch (IOException e) {
  403.             cleanupTemporaryFiles();
  404.             keep.unlock();
  405.             throw new IOException(MessageFormat.format(
  406.                     JGitText.get().cannotMovePackTo, finalPack), e);
  407.         }

  408.         try {
  409.             FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE);
  410.         } catch (IOException e) {
  411.             cleanupTemporaryFiles();
  412.             keep.unlock();
  413.             if (!finalPack.delete())
  414.                 finalPack.deleteOnExit();
  415.             throw new IOException(MessageFormat.format(
  416.                     JGitText.get().cannotMoveIndexTo, finalIdx), e);
  417.         }

  418.         boolean interrupted = false;
  419.         try {
  420.             FileSnapshot snapshot = FileSnapshot.save(finalPack);
  421.             if (pconfig.doWaitPreventRacyPack(snapshot.size())) {
  422.                 snapshot.waitUntilNotRacy();
  423.             }
  424.         } catch (InterruptedException e) {
  425.             interrupted = true;
  426.         }
  427.         try {
  428.             newPack = db.openPack(finalPack);
  429.         } catch (IOException err) {
  430.             keep.unlock();
  431.             if (finalPack.exists())
  432.                 FileUtils.delete(finalPack);
  433.             if (finalIdx.exists())
  434.                 FileUtils.delete(finalIdx);
  435.             throw err;
  436.         } finally {
  437.             if (interrupted) {
  438.                 // Re-set interrupted flag
  439.                 Thread.currentThread().interrupt();
  440.             }
  441.         }

  442.         return lockMessage != null ? keep : null;
  443.     }
  444. }