PackDirectory.java

  1. /*
  2.  * Copyright (C) 2009, 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.file;

  11. import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  12. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  13. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;

  14. import java.io.File;
  15. import java.io.FileNotFoundException;
  16. import java.io.IOException;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.EnumMap;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.concurrent.atomic.AtomicReference;

  28. import org.eclipse.jgit.annotations.Nullable;
  29. import org.eclipse.jgit.errors.CorruptObjectException;
  30. import org.eclipse.jgit.errors.PackInvalidException;
  31. import org.eclipse.jgit.errors.PackMismatchException;
  32. import org.eclipse.jgit.internal.JGitText;
  33. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  34. import org.eclipse.jgit.internal.storage.pack.PackExt;
  35. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  36. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  37. import org.eclipse.jgit.lib.AnyObjectId;
  38. import org.eclipse.jgit.lib.Config;
  39. import org.eclipse.jgit.lib.ConfigConstants;
  40. import org.eclipse.jgit.lib.ObjectId;
  41. import org.eclipse.jgit.lib.ObjectLoader;
  42. import org.eclipse.jgit.util.FileUtils;
  43. import org.slf4j.Logger;
  44. import org.slf4j.LoggerFactory;

  45. /**
  46.  * Traditional file system packed objects directory handler.
  47.  * <p>
  48.  * This is the {@link org.eclipse.jgit.internal.storage.file.Pack}s object
  49.  * representation for a Git object database, where objects are stored in
  50.  * compressed containers known as
  51.  * {@link org.eclipse.jgit.internal.storage.file.Pack}s.
  52.  */
  53. class PackDirectory {
  54.     private final static Logger LOG = LoggerFactory
  55.             .getLogger(PackDirectory.class);

  56.     private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
  57.             new Pack[0]);

  58.     private final Config config;

  59.     private final File directory;

  60.     private final AtomicReference<PackList> packList;

  61.     /**
  62.      * Initialize a reference to an on-disk 'pack' directory.
  63.      *
  64.      * @param config
  65.      *            configuration this directory consults for write settings.
  66.      * @param directory
  67.      *            the location of the {@code pack} directory.
  68.      */
  69.     PackDirectory(Config config, File directory) {
  70.         this.config = config;
  71.         this.directory = directory;
  72.         packList = new AtomicReference<>(NO_PACKS);
  73.     }

  74.     /**
  75.      * Getter for the field {@code directory}.
  76.      *
  77.      * @return the location of the {@code pack} directory.
  78.      */
  79.     File getDirectory() {
  80.         return directory;
  81.     }

  82.     void create() throws IOException {
  83.         FileUtils.mkdir(directory);
  84.     }

  85.     void close() {
  86.         PackList packs = packList.get();
  87.         if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
  88.             for (Pack p : packs.packs) {
  89.                 p.close();
  90.             }
  91.         }
  92.     }

  93.     Collection<Pack> getPacks() {
  94.         PackList list = packList.get();
  95.         if (list == NO_PACKS) {
  96.             list = scanPacks(list);
  97.         }
  98.         Pack[] packs = list.packs;
  99.         return Collections.unmodifiableCollection(Arrays.asList(packs));
  100.     }

  101.     /** {@inheritDoc} */
  102.     @Override
  103.     public String toString() {
  104.         return "PackDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
  105.     }

  106.     /**
  107.      * Does the requested object exist in this PackDirectory?
  108.      *
  109.      * @param objectId
  110.      *            identity of the object to test for existence of.
  111.      * @return {@code true} if the specified object is stored in this PackDirectory.
  112.      */
  113.     boolean has(AnyObjectId objectId) {
  114.         return getPack(objectId) != null;
  115.     }

  116.     /**
  117.      * Get the {@link org.eclipse.jgit.internal.storage.file.Pack} for the
  118.      * specified object if it is stored in this PackDirectory.
  119.      *
  120.      * @param objectId
  121.      *            identity of the object to find the Pack for.
  122.      * @return {@link org.eclipse.jgit.internal.storage.file.Pack} which
  123.      *         contains the specified object or {@code null} if it is not stored
  124.      *         in this PackDirectory.
  125.      */
  126.     @Nullable
  127.     Pack getPack(AnyObjectId objectId) {
  128.         PackList pList;
  129.         do {
  130.             pList = packList.get();
  131.             for (Pack p : pList.packs) {
  132.                 try {
  133.                     if (p.hasObject(objectId)) {
  134.                         return p;
  135.                     }
  136.                 } catch (IOException e) {
  137.                     // The hasObject call should have only touched the index, so
  138.                     // any failure here indicates the index is unreadable by
  139.                     // this process, and the pack is likewise not readable.
  140.                     LOG.warn(MessageFormat.format(
  141.                             JGitText.get().unableToReadPackfile,
  142.                             p.getPackFile().getAbsolutePath()), e);
  143.                     remove(p);
  144.                 }
  145.             }
  146.         } while (searchPacksAgain(pList));
  147.         return null;
  148.     }

  149.     /**
  150.      * Find objects matching the prefix abbreviation.
  151.      *
  152.      * @param matches
  153.      *            set to add any located ObjectIds to. This is an output
  154.      *            parameter.
  155.      * @param id
  156.      *            prefix to search for.
  157.      * @param matchLimit
  158.      *            maximum number of results to return. At most this many
  159.      *            ObjectIds should be added to matches before returning.
  160.      * @return {@code true} if the matches were exhausted before reaching
  161.      *         {@code maxLimit}.
  162.      */
  163.     boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  164.             int matchLimit) {
  165.         // Go through the packs once. If we didn't find any resolutions
  166.         // scan for new packs and check once more.
  167.         int oldSize = matches.size();
  168.         PackList pList;
  169.         do {
  170.             pList = packList.get();
  171.             for (Pack p : pList.packs) {
  172.                 try {
  173.                     p.resolve(matches, id, matchLimit);
  174.                     p.resetTransientErrorCount();
  175.                 } catch (IOException e) {
  176.                     handlePackError(e, p);
  177.                 }
  178.                 if (matches.size() > matchLimit) {
  179.                     return false;
  180.                 }
  181.             }
  182.         } while (matches.size() == oldSize && searchPacksAgain(pList));
  183.         return true;
  184.     }

  185.     ObjectLoader open(WindowCursor curs, AnyObjectId objectId) {
  186.         PackList pList;
  187.         do {
  188.             SEARCH: for (;;) {
  189.                 pList = packList.get();
  190.                 for (Pack p : pList.packs) {
  191.                     try {
  192.                         ObjectLoader ldr = p.get(curs, objectId);
  193.                         p.resetTransientErrorCount();
  194.                         if (ldr != null)
  195.                             return ldr;
  196.                     } catch (PackMismatchException e) {
  197.                         // Pack was modified; refresh the entire pack list.
  198.                         if (searchPacksAgain(pList)) {
  199.                             continue SEARCH;
  200.                         }
  201.                     } catch (IOException e) {
  202.                         handlePackError(e, p);
  203.                     }
  204.                 }
  205.                 break SEARCH;
  206.             }
  207.         } while (searchPacksAgain(pList));
  208.         return null;
  209.     }

  210.     long getSize(WindowCursor curs, AnyObjectId id) {
  211.         PackList pList;
  212.         do {
  213.             SEARCH: for (;;) {
  214.                 pList = packList.get();
  215.                 for (Pack p : pList.packs) {
  216.                     try {
  217.                         long len = p.getObjectSize(curs, id);
  218.                         p.resetTransientErrorCount();
  219.                         if (0 <= len) {
  220.                             return len;
  221.                         }
  222.                     } catch (PackMismatchException e) {
  223.                         // Pack was modified; refresh the entire pack list.
  224.                         if (searchPacksAgain(pList)) {
  225.                             continue SEARCH;
  226.                         }
  227.                     } catch (IOException e) {
  228.                         handlePackError(e, p);
  229.                     }
  230.                 }
  231.                 break SEARCH;
  232.             }
  233.         } while (searchPacksAgain(pList));
  234.         return -1;
  235.     }

  236.     void selectRepresentation(PackWriter packer, ObjectToPack otp,
  237.             WindowCursor curs) {
  238.         PackList pList = packList.get();
  239.         SEARCH: for (;;) {
  240.             for (Pack p : pList.packs) {
  241.                 try {
  242.                     LocalObjectRepresentation rep = p.representation(curs, otp);
  243.                     p.resetTransientErrorCount();
  244.                     if (rep != null) {
  245.                         packer.select(otp, rep);
  246.                     }
  247.                 } catch (PackMismatchException e) {
  248.                     // Pack was modified; refresh the entire pack list.
  249.                     //
  250.                     pList = scanPacks(pList);
  251.                     continue SEARCH;
  252.                 } catch (IOException e) {
  253.                     handlePackError(e, p);
  254.                 }
  255.             }
  256.             break SEARCH;
  257.         }
  258.     }

  259.     private void handlePackError(IOException e, Pack p) {
  260.         String warnTmpl = null;
  261.         int transientErrorCount = 0;
  262.         String errTmpl = JGitText.get().exceptionWhileReadingPack;
  263.         if ((e instanceof CorruptObjectException)
  264.                 || (e instanceof PackInvalidException)) {
  265.             warnTmpl = JGitText.get().corruptPack;
  266.             LOG.warn(MessageFormat.format(warnTmpl,
  267.                     p.getPackFile().getAbsolutePath()), e);
  268.             // Assume the pack is corrupted, and remove it from the list.
  269.             remove(p);
  270.         } else if (e instanceof FileNotFoundException) {
  271.             if (p.getPackFile().exists()) {
  272.                 errTmpl = JGitText.get().packInaccessible;
  273.                 transientErrorCount = p.incrementTransientErrorCount();
  274.             } else {
  275.                 warnTmpl = JGitText.get().packWasDeleted;
  276.                 remove(p);
  277.             }
  278.         } else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
  279.             warnTmpl = JGitText.get().packHandleIsStale;
  280.             remove(p);
  281.         } else {
  282.             transientErrorCount = p.incrementTransientErrorCount();
  283.         }
  284.         if (warnTmpl != null) {
  285.             LOG.warn(MessageFormat.format(warnTmpl,
  286.                     p.getPackFile().getAbsolutePath()), e);
  287.         } else {
  288.             if (doLogExponentialBackoff(transientErrorCount)) {
  289.                 // Don't remove the pack from the list, as the error may be
  290.                 // transient.
  291.                 LOG.error(MessageFormat.format(errTmpl,
  292.                         p.getPackFile().getAbsolutePath(),
  293.                         Integer.valueOf(transientErrorCount)), e);
  294.             }
  295.         }
  296.     }

  297.     /**
  298.      * @param n
  299.      *            count of consecutive failures
  300.      * @return @{code true} if i is a power of 2
  301.      */
  302.     private boolean doLogExponentialBackoff(int n) {
  303.         return (n & (n - 1)) == 0;
  304.     }

  305.     boolean searchPacksAgain(PackList old) {
  306.         // Whether to trust the pack folder's modification time. If set
  307.         // to false we will always scan the .git/objects/pack folder to
  308.         // check for new pack files. If set to true (default) we use the
  309.         // lastmodified attribute of the folder and assume that no new
  310.         // pack files can be in this folder if his modification time has
  311.         // not changed.
  312.         boolean trustFolderStat = config.getBoolean(
  313.                 ConfigConstants.CONFIG_CORE_SECTION,
  314.                 ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);

  315.         return ((!trustFolderStat) || old.snapshot.isModified(directory))
  316.                 && old != scanPacks(old);
  317.     }

  318.     void insert(Pack pack) {
  319.         PackList o, n;
  320.         do {
  321.             o = packList.get();

  322.             // If the pack in question is already present in the list
  323.             // (picked up by a concurrent thread that did a scan?) we
  324.             // do not want to insert it a second time.
  325.             //
  326.             final Pack[] oldList = o.packs;
  327.             final String name = pack.getPackFile().getName();
  328.             for (Pack p : oldList) {
  329.                 if (name.equals(p.getPackFile().getName())) {
  330.                     return;
  331.                 }
  332.             }

  333.             final Pack[] newList = new Pack[1 + oldList.length];
  334.             newList[0] = pack;
  335.             System.arraycopy(oldList, 0, newList, 1, oldList.length);
  336.             n = new PackList(o.snapshot, newList);
  337.         } while (!packList.compareAndSet(o, n));
  338.     }

  339.     private void remove(Pack deadPack) {
  340.         PackList o, n;
  341.         do {
  342.             o = packList.get();

  343.             final Pack[] oldList = o.packs;
  344.             final int j = indexOf(oldList, deadPack);
  345.             if (j < 0) {
  346.                 break;
  347.             }

  348.             final Pack[] newList = new Pack[oldList.length - 1];
  349.             System.arraycopy(oldList, 0, newList, 0, j);
  350.             System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
  351.             n = new PackList(o.snapshot, newList);
  352.         } while (!packList.compareAndSet(o, n));
  353.         deadPack.close();
  354.     }

  355.     private static int indexOf(Pack[] list, Pack pack) {
  356.         for (int i = 0; i < list.length; i++) {
  357.             if (list[i] == pack) {
  358.                 return i;
  359.             }
  360.         }
  361.         return -1;
  362.     }

  363.     private PackList scanPacks(PackList original) {
  364.         synchronized (packList) {
  365.             PackList o, n;
  366.             do {
  367.                 o = packList.get();
  368.                 if (o != original) {
  369.                     // Another thread did the scan for us, while we
  370.                     // were blocked on the monitor above.
  371.                     //
  372.                     return o;
  373.                 }
  374.                 n = scanPacksImpl(o);
  375.                 if (n == o) {
  376.                     return n;
  377.                 }
  378.             } while (!packList.compareAndSet(o, n));
  379.             return n;
  380.         }
  381.     }

  382.     private PackList scanPacksImpl(PackList old) {
  383.         final Map<String, Pack> forReuse = reuseMap(old);
  384.         final FileSnapshot snapshot = FileSnapshot.save(directory);
  385.         Map<String, Map<PackExt, PackFile>> packFilesByExtById = getPackFilesByExtById();
  386.         List<Pack> list = new ArrayList<>(packFilesByExtById.size());
  387.         boolean foundNew = false;
  388.         for (Map<PackExt, PackFile> packFilesByExt : packFilesByExtById
  389.                 .values()) {
  390.             PackFile packFile = packFilesByExt.get(PACK);
  391.             if (packFile == null || !packFilesByExt.containsKey(INDEX)) {
  392.                 // Sometimes C Git's HTTP fetch transport leaves a
  393.                 // .idx file behind and does not download the .pack.
  394.                 // We have to skip over such useless indexes.
  395.                 // Also skip if we don't have any index for this id
  396.                 continue;
  397.             }

  398.             Pack oldPack = forReuse.get(packFile.getName());
  399.             if (oldPack != null
  400.                     && !oldPack.getFileSnapshot().isModified(packFile)) {
  401.                 forReuse.remove(packFile.getName());
  402.                 list.add(oldPack);
  403.                 continue;
  404.             }

  405.             list.add(new Pack(packFile, packFilesByExt.get(BITMAP_INDEX)));
  406.             foundNew = true;
  407.         }

  408.         // If we did not discover any new files, the modification time was not
  409.         // changed, and we did not remove any files, then the set of files is
  410.         // the same as the set we were given. Instead of building a new object
  411.         // return the same collection.
  412.         //
  413.         if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
  414.             old.snapshot.setClean(snapshot);
  415.             return old;
  416.         }

  417.         for (Pack p : forReuse.values()) {
  418.             p.close();
  419.         }

  420.         if (list.isEmpty()) {
  421.             return new PackList(snapshot, NO_PACKS.packs);
  422.         }

  423.         final Pack[] r = list.toArray(new Pack[0]);
  424.         Arrays.sort(r, Pack.SORT);
  425.         return new PackList(snapshot, r);
  426.     }

  427.     private static Map<String, Pack> reuseMap(PackList old) {
  428.         final Map<String, Pack> forReuse = new HashMap<>();
  429.         for (Pack p : old.packs) {
  430.             if (p.invalid()) {
  431.                 // The pack instance is corrupted, and cannot be safely used
  432.                 // again. Do not include it in our reuse map.
  433.                 //
  434.                 p.close();
  435.                 continue;
  436.             }

  437.             final Pack prior = forReuse.put(p.getPackFile().getName(), p);
  438.             if (prior != null) {
  439.                 // This should never occur. It should be impossible for us
  440.                 // to have two pack files with the same name, as all of them
  441.                 // came out of the same directory. If it does, we promised to
  442.                 // close any PackFiles we did not reuse, so close the second,
  443.                 // readers are likely to be actively using the first.
  444.                 //
  445.                 forReuse.put(prior.getPackFile().getName(), prior);
  446.                 p.close();
  447.             }
  448.         }
  449.         return forReuse;
  450.     }

  451.     /**
  452.      * Scans the pack directory for
  453.      * {@link org.eclipse.jgit.internal.storage.file.PackFile}s and returns them
  454.      * organized by their extensions and their pack ids
  455.      *
  456.      * Skips files in the directory that we cannot create a
  457.      * {@link org.eclipse.jgit.internal.storage.file.PackFile} for.
  458.      *
  459.      * @return a map of {@link org.eclipse.jgit.internal.storage.file.PackFile}s
  460.      *         and {@link org.eclipse.jgit.internal.storage.pack.PackExt}s keyed
  461.      *         by pack ids
  462.      */
  463.     private Map<String, Map<PackExt, PackFile>> getPackFilesByExtById() {
  464.         final String[] nameList = directory.list();
  465.         if (nameList == null) {
  466.             return Collections.emptyMap();
  467.         }
  468.         Map<String, Map<PackExt, PackFile>> packFilesByExtById = new HashMap<>(
  469.                 nameList.length / 2); // assume roughly 2 files per id
  470.         for (String name : nameList) {
  471.             try {
  472.                 PackFile pack = new PackFile(directory, name);
  473.                 if (pack.getPackExt() != null) {
  474.                     Map<PackExt, PackFile> packByExt = packFilesByExtById
  475.                             .get(pack.getId());
  476.                     if (packByExt == null) {
  477.                         packByExt = new EnumMap<>(PackExt.class);
  478.                         packFilesByExtById.put(pack.getId(), packByExt);
  479.                     }
  480.                     packByExt.put(pack.getPackExt(), pack);
  481.                 }
  482.             } catch (IllegalArgumentException e) {
  483.                 continue;
  484.             }
  485.         }
  486.         return packFilesByExtById;
  487.     }

  488.     static final class PackList {
  489.         /** State just before reading the pack directory. */
  490.         final FileSnapshot snapshot;

  491.         /** All known packs, sorted by {@link Pack#SORT}. */
  492.         final Pack[] packs;

  493.         PackList(FileSnapshot monitor, Pack[] packs) {
  494.             this.snapshot = monitor;
  495.             this.packs = packs;
  496.         }
  497.     }
  498. }