FS.java

  1. /*
  2.  * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> 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.util;

  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static java.time.Instant.EPOCH;

  13. import java.io.BufferedReader;
  14. import java.io.ByteArrayInputStream;
  15. import java.io.Closeable;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.InputStreamReader;
  20. import java.io.OutputStream;
  21. import java.io.OutputStreamWriter;
  22. import java.io.Writer;
  23. import java.nio.file.AccessDeniedException;
  24. import java.nio.file.FileStore;
  25. import java.nio.file.Files;
  26. import java.nio.file.InvalidPathException;
  27. import java.nio.file.Path;
  28. import java.nio.file.attribute.BasicFileAttributes;
  29. import java.nio.file.attribute.FileTime;
  30. import java.security.AccessControlException;
  31. import java.security.AccessController;
  32. import java.security.PrivilegedAction;
  33. import java.text.MessageFormat;
  34. import java.time.Duration;
  35. import java.time.Instant;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.HashMap;
  39. import java.util.Map;
  40. import java.util.Objects;
  41. import java.util.Optional;
  42. import java.util.UUID;
  43. import java.util.concurrent.CancellationException;
  44. import java.util.concurrent.CompletableFuture;
  45. import java.util.concurrent.ConcurrentHashMap;
  46. import java.util.concurrent.ExecutionException;
  47. import java.util.concurrent.ExecutorService;
  48. import java.util.concurrent.Executors;
  49. import java.util.concurrent.LinkedBlockingQueue;
  50. import java.util.concurrent.ThreadPoolExecutor;
  51. import java.util.concurrent.TimeUnit;
  52. import java.util.concurrent.TimeoutException;
  53. import java.util.concurrent.atomic.AtomicBoolean;
  54. import java.util.concurrent.atomic.AtomicInteger;
  55. import java.util.concurrent.atomic.AtomicReference;
  56. import java.util.concurrent.locks.Lock;
  57. import java.util.concurrent.locks.ReentrantLock;
  58. import java.util.regex.Matcher;
  59. import java.util.regex.Pattern;

  60. import org.eclipse.jgit.annotations.NonNull;
  61. import org.eclipse.jgit.annotations.Nullable;
  62. import org.eclipse.jgit.api.errors.JGitInternalException;
  63. import org.eclipse.jgit.errors.CommandFailedException;
  64. import org.eclipse.jgit.errors.ConfigInvalidException;
  65. import org.eclipse.jgit.errors.LockFailedException;
  66. import org.eclipse.jgit.internal.JGitText;
  67. import org.eclipse.jgit.internal.storage.file.FileSnapshot;
  68. import org.eclipse.jgit.lib.Config;
  69. import org.eclipse.jgit.lib.ConfigConstants;
  70. import org.eclipse.jgit.lib.Constants;
  71. import org.eclipse.jgit.lib.Repository;
  72. import org.eclipse.jgit.lib.StoredConfig;
  73. import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
  74. import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
  75. import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
  76. import org.eclipse.jgit.util.ProcessResult.Status;
  77. import org.slf4j.Logger;
  78. import org.slf4j.LoggerFactory;

  79. /**
  80.  * Abstraction to support various file system operations not in Java.
  81.  */
  82. public abstract class FS {
  83.     private static final Logger LOG = LoggerFactory.getLogger(FS.class);

  84.     /**
  85.      * An empty array of entries, suitable as a return value for
  86.      * {@link #list(File, FileModeStrategy)}.
  87.      *
  88.      * @since 5.0
  89.      */
  90.     protected static final Entry[] NO_ENTRIES = {};

  91.     private static final Pattern VERSION = Pattern
  92.             .compile("\\s(\\d+)\\.(\\d+)\\.(\\d+)"); //$NON-NLS-1$

  93.     private volatile Boolean supportSymlinks;

  94.     /**
  95.      * This class creates FS instances. It will be overridden by a Java7 variant
  96.      * if such can be detected in {@link #detect(Boolean)}.
  97.      *
  98.      * @since 3.0
  99.      */
  100.     public static class FSFactory {
  101.         /**
  102.          * Constructor
  103.          */
  104.         protected FSFactory() {
  105.             // empty
  106.         }

  107.         /**
  108.          * Detect the file system
  109.          *
  110.          * @param cygwinUsed
  111.          * @return FS instance
  112.          */
  113.         public FS detect(Boolean cygwinUsed) {
  114.             if (SystemReader.getInstance().isWindows()) {
  115.                 if (cygwinUsed == null) {
  116.                     cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  117.                 }
  118.                 if (cygwinUsed.booleanValue()) {
  119.                     return new FS_Win32_Cygwin();
  120.                 }
  121.                 return new FS_Win32();
  122.             }
  123.             return new FS_POSIX();
  124.         }
  125.     }

  126.     /**
  127.      * Result of an executed process. The caller is responsible to close the
  128.      * contained {@link TemporaryBuffer}s
  129.      *
  130.      * @since 4.2
  131.      */
  132.     public static class ExecutionResult {
  133.         private TemporaryBuffer stdout;

  134.         private TemporaryBuffer stderr;

  135.         private int rc;

  136.         /**
  137.          * @param stdout
  138.          * @param stderr
  139.          * @param rc
  140.          */
  141.         public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
  142.                 int rc) {
  143.             this.stdout = stdout;
  144.             this.stderr = stderr;
  145.             this.rc = rc;
  146.         }

  147.         /**
  148.          * @return buffered standard output stream
  149.          */
  150.         public TemporaryBuffer getStdout() {
  151.             return stdout;
  152.         }

  153.         /**
  154.          * @return buffered standard error stream
  155.          */
  156.         public TemporaryBuffer getStderr() {
  157.             return stderr;
  158.         }

  159.         /**
  160.          * @return the return code of the process
  161.          */
  162.         public int getRc() {
  163.             return rc;
  164.         }
  165.     }

  166.     /**
  167.      * Attributes of FileStores on this system
  168.      *
  169.      * @since 5.1.9
  170.      */
  171.     public static final class FileStoreAttributes {

  172.         /**
  173.          * Marker to detect undefined values when reading from the config file.
  174.          */
  175.         private static final Duration UNDEFINED_DURATION = Duration
  176.                 .ofNanos(Long.MAX_VALUE);

  177.         /**
  178.          * Fallback filesystem timestamp resolution. The worst case timestamp
  179.          * resolution on FAT filesystems is 2 seconds.
  180.          * <p>
  181.          * Must be at least 1 second.
  182.          * </p>
  183.          */
  184.         public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
  185.                 .ofMillis(2000);

  186.         /**
  187.          * Fallback FileStore attributes used when we can't measure the
  188.          * filesystem timestamp resolution. The last modified time granularity
  189.          * of FAT filesystems is 2 seconds.
  190.          */
  191.         public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
  192.                 FALLBACK_TIMESTAMP_RESOLUTION);

  193.         private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS
  194.                 .toNanos(1);

  195.         private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS
  196.                 .toNanos(1);

  197.         private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1);

  198.         /**
  199.          * Minimum file system timestamp resolution granularity to check, in
  200.          * nanoseconds. Should be a positive power of ten smaller than
  201.          * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e.,
  202.          * minimum value is 1 nanosecond.
  203.          * <p>
  204.          * Currently set to 1 microsecond, but could also be lower still.
  205.          * </p>
  206.          */
  207.         private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND;

  208.         private static final String JAVA_VERSION_PREFIX = System
  209.                 .getProperty("java.vendor") + '|' //$NON-NLS-1$
  210.                 + System.getProperty("java.version") + '|'; //$NON-NLS-1$

  211.         private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
  212.                 .ofMillis(10);

  213.         private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();

  214.         private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
  215.                 100, 0.2f);

  216.         private static final AtomicBoolean background = new AtomicBoolean();

  217.         private static final Map<FileStore, Lock> locks = new ConcurrentHashMap<>();

  218.         private static final AtomicInteger threadNumber = new AtomicInteger(1);

  219.         /**
  220.          * Don't use the default thread factory of the ForkJoinPool for the
  221.          * CompletableFuture; it runs without any privileges, which causes
  222.          * trouble if a SecurityManager is present.
  223.          * <p>
  224.          * Instead use normal daemon threads. They'll belong to the
  225.          * SecurityManager's thread group, or use the one of the calling thread,
  226.          * as appropriate.
  227.          * </p>
  228.          *
  229.          * @see java.util.concurrent.Executors#newCachedThreadPool()
  230.          */
  231.         private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor(
  232.                 0, 5, 30L, TimeUnit.SECONDS,
  233.                 new LinkedBlockingQueue<Runnable>(),
  234.                 runnable -> {
  235.                     Thread t = new Thread(runnable,
  236.                             "JGit-FileStoreAttributeReader-" //$NON-NLS-1$
  237.                             + threadNumber.getAndIncrement());
  238.                     // Make sure these threads don't prevent application/JVM
  239.                     // shutdown.
  240.                     t.setDaemon(true);
  241.                     return t;
  242.                 });

  243.         /**
  244.          * Use a separate executor with at most one thread to synchronize
  245.          * writing to the config. We write asynchronously since the config
  246.          * itself might be on a different file system, which might otherwise
  247.          * lead to locking problems.
  248.          * <p>
  249.          * Writing the config must not use a daemon thread, otherwise we may
  250.          * leave an inconsistent state on disk when the JVM shuts down. Use a
  251.          * small keep-alive time to avoid delays on shut-down.
  252.          * </p>
  253.          */
  254.         private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor(
  255.                 0, 1, 1L, TimeUnit.MILLISECONDS,
  256.                 new LinkedBlockingQueue<Runnable>(),
  257.                 runnable -> {
  258.                     Thread t = new Thread(runnable,
  259.                             "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$
  260.                             + threadNumber.getAndIncrement());
  261.                     // Make sure these threads do finish
  262.                     t.setDaemon(false);
  263.                     return t;
  264.                 });

  265.         static {
  266.             // Shut down the SAVE_RUNNER on System.exit()
  267.             try {
  268.                 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  269.                     try {
  270.                         SAVE_RUNNER.shutdownNow();
  271.                         SAVE_RUNNER.awaitTermination(100,
  272.                                 TimeUnit.MILLISECONDS);
  273.                     } catch (Exception e) {
  274.                         // Ignore; we're shutting down
  275.                     }
  276.                 }));
  277.             } catch (IllegalStateException e) {
  278.                 // ignore - may fail if shutdown is already in progress
  279.             }
  280.         }

  281.         /**
  282.          * Whether FileStore attributes should be determined asynchronously
  283.          *
  284.          * @param async
  285.          *            whether FileStore attributes should be determined
  286.          *            asynchronously. If false access to cached attributes may
  287.          *            block for some seconds for the first call per FileStore
  288.          * @since 5.6.2
  289.          */
  290.         public static void setBackground(boolean async) {
  291.             background.set(async);
  292.         }

  293.         /**
  294.          * Configures size and purge factor of the path-based cache for file
  295.          * system attributes. Caching of file system attributes avoids recurring
  296.          * lookup of @{code FileStore} of files which may be expensive on some
  297.          * platforms.
  298.          *
  299.          * @param maxSize
  300.          *            maximum size of the cache, default is 100
  301.          * @param purgeFactor
  302.          *            when the size of the map reaches maxSize the oldest
  303.          *            entries will be purged to free up some space for new
  304.          *            entries, {@code purgeFactor} is the fraction of
  305.          *            {@code maxSize} to purge when this happens
  306.          * @since 5.1.9
  307.          */
  308.         public static void configureAttributesPathCache(int maxSize,
  309.                 float purgeFactor) {
  310.             FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
  311.         }

  312.         /**
  313.          * Get the FileStoreAttributes for the given FileStore
  314.          *
  315.          * @param path
  316.          *            file residing in the FileStore to get attributes for
  317.          * @return FileStoreAttributes for the given path.
  318.          */
  319.         public static FileStoreAttributes get(Path path) {
  320.             try {
  321.                 path = path.toAbsolutePath();
  322.                 Path dir = Files.isDirectory(path) ? path : path.getParent();
  323.                 if (dir == null) {
  324.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  325.                 }
  326.                 FileStoreAttributes cached = attrCacheByPath.get(dir);
  327.                 if (cached != null) {
  328.                     return cached;
  329.                 }
  330.                 FileStoreAttributes attrs = getFileStoreAttributes(dir);
  331.                 if (attrs == null) {
  332.                     // Don't cache, result might be late
  333.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  334.                 }
  335.                 attrCacheByPath.put(dir, attrs);
  336.                 return attrs;
  337.             } catch (SecurityException e) {
  338.                 return FALLBACK_FILESTORE_ATTRIBUTES;
  339.             }
  340.         }

  341.         private static FileStoreAttributes getFileStoreAttributes(Path dir) {
  342.             FileStore s;
  343.             try {
  344.                 if (Files.exists(dir)) {
  345.                     s = Files.getFileStore(dir);
  346.                     FileStoreAttributes c = attributeCache.get(s);
  347.                     if (c != null) {
  348.                         return c;
  349.                     }
  350.                     if (!Files.isWritable(dir)) {
  351.                         // cannot measure resolution in a read-only directory
  352.                         LOG.debug(
  353.                                 "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
  354.                                 Thread.currentThread(), dir);
  355.                         return FALLBACK_FILESTORE_ATTRIBUTES;
  356.                     }
  357.                 } else {
  358.                     // cannot determine FileStore of an unborn directory
  359.                     LOG.debug(
  360.                             "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
  361.                             Thread.currentThread(), dir);
  362.                     return FALLBACK_FILESTORE_ATTRIBUTES;
  363.                 }

  364.                 CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
  365.                         .supplyAsync(() -> {
  366.                             Lock lock = locks.computeIfAbsent(s,
  367.                                     l -> new ReentrantLock());
  368.                             if (!lock.tryLock()) {
  369.                                 LOG.debug(
  370.                                         "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
  371.                                         Thread.currentThread(), dir);
  372.                                 return Optional.empty();
  373.                             }
  374.                             Optional<FileStoreAttributes> attributes = Optional
  375.                                     .empty();
  376.                             try {
  377.                                 // Some earlier future might have set the value
  378.                                 // and removed itself since we checked for the
  379.                                 // value above. Hence check cache again.
  380.                                 FileStoreAttributes c = attributeCache.get(s);
  381.                                 if (c != null) {
  382.                                     return Optional.of(c);
  383.                                 }
  384.                                 attributes = readFromConfig(s);
  385.                                 if (attributes.isPresent()) {
  386.                                     attributeCache.put(s, attributes.get());
  387.                                     return attributes;
  388.                                 }

  389.                                 Optional<Duration> resolution = measureFsTimestampResolution(
  390.                                         s, dir);
  391.                                 if (resolution.isPresent()) {
  392.                                     c = new FileStoreAttributes(
  393.                                             resolution.get());
  394.                                     attributeCache.put(s, c);
  395.                                     // for high timestamp resolution measure
  396.                                     // minimal racy interval
  397.                                     if (c.fsTimestampResolution
  398.                                             .toNanos() < 100_000_000L) {
  399.                                         c.minimalRacyInterval = measureMinimalRacyInterval(
  400.                                                 dir);
  401.                                     }
  402.                                     if (LOG.isDebugEnabled()) {
  403.                                         LOG.debug(c.toString());
  404.                                     }
  405.                                     FileStoreAttributes newAttrs = c;
  406.                                     SAVE_RUNNER.execute(
  407.                                             () -> saveToConfig(s, newAttrs));
  408.                                 }
  409.                                 attributes = Optional.of(c);
  410.                             } finally {
  411.                                 lock.unlock();
  412.                                 locks.remove(s);
  413.                             }
  414.                             return attributes;
  415.                         }, FUTURE_RUNNER);
  416.                 f = f.exceptionally(e -> {
  417.                     LOG.error(e.getLocalizedMessage(), e);
  418.                     return Optional.empty();
  419.                 });
  420.                 // even if measuring in background wait a little - if the result
  421.                 // arrives, it's better than returning the large fallback
  422.                 boolean runInBackground = background.get();
  423.                 Optional<FileStoreAttributes> d = runInBackground ? f.get(
  424.                         100, TimeUnit.MILLISECONDS) : f.get();
  425.                 if (d.isPresent()) {
  426.                     return d.get();
  427.                 } else if (runInBackground) {
  428.                     // return null until measurement is finished
  429.                     return null;
  430.                 }
  431.                 // fall through and return fallback
  432.             } catch (IOException | ExecutionException | CancellationException e) {
  433.                 LOG.error(e.getMessage(), e);
  434.             } catch (TimeoutException | SecurityException e) {
  435.                 // use fallback
  436.             } catch (InterruptedException e) {
  437.                 LOG.error(e.getMessage(), e);
  438.                 Thread.currentThread().interrupt();
  439.             }
  440.             LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$
  441.                     Thread.currentThread(), dir);
  442.             return FALLBACK_FILESTORE_ATTRIBUTES;
  443.         }

  444.         @SuppressWarnings("boxing")
  445.         private static Duration measureMinimalRacyInterval(Path dir) {
  446.             LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
  447.                     Thread.currentThread(), dir);
  448.             int n = 0;
  449.             int failures = 0;
  450.             long racyNanos = 0;
  451.             ArrayList<Long> deltas = new ArrayList<>();
  452.             Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  453.             Instant end = Instant.now().plusSeconds(3);
  454.             try {
  455.                 probe.toFile().deleteOnExit();
  456.                 Files.createFile(probe);
  457.                 do {
  458.                     n++;
  459.                     write(probe, "a"); //$NON-NLS-1$
  460.                     FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
  461.                     read(probe);
  462.                     write(probe, "b"); //$NON-NLS-1$
  463.                     if (!snapshot.isModified(probe.toFile())) {
  464.                         deltas.add(Long.valueOf(snapshot.lastDelta()));
  465.                         racyNanos = snapshot.lastRacyThreshold();
  466.                         failures++;
  467.                     }
  468.                 } while (Instant.now().compareTo(end) < 0);
  469.             } catch (IOException e) {
  470.                 LOG.error(e.getMessage(), e);
  471.                 return FALLBACK_MIN_RACY_INTERVAL;
  472.             } finally {
  473.                 deleteProbe(probe);
  474.             }
  475.             if (failures > 0) {
  476.                 Stats stats = new Stats();
  477.                 for (Long d : deltas) {
  478.                     stats.add(d);
  479.                 }
  480.                 LOG.debug(
  481.                         "delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$
  482.                                 + "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$
  483.                                 + " delta max [ns], delta avg [ns]," //$NON-NLS-1$
  484.                                 + " delta stddev [ns]\n" //$NON-NLS-1$
  485.                                 + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
  486.                         n, failures, racyNanos, stats.min(), stats.max(),
  487.                         stats.avg(), stats.stddev());
  488.                 return Duration
  489.                         .ofNanos(Double.valueOf(stats.max()).longValue());
  490.             }
  491.             // since no failures occurred using the measured filesystem
  492.             // timestamp resolution there is no need for minimal racy interval
  493.             LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$
  494.                     Thread.currentThread());
  495.             return Duration.ZERO;
  496.         }

  497.         private static void write(Path p, String body) throws IOException {
  498.             Path parent = p.getParent();
  499.             if (parent != null) {
  500.                 FileUtils.mkdirs(parent.toFile(), true);
  501.             }
  502.             try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
  503.                     UTF_8)) {
  504.                 w.write(body);
  505.             }
  506.         }

  507.         private static String read(Path p) throws IOException {
  508.             byte[] body = IO.readFully(p.toFile());
  509.             return new String(body, 0, body.length, UTF_8);
  510.         }

  511.         private static Optional<Duration> measureFsTimestampResolution(
  512.             FileStore s, Path dir) {
  513.             if (LOG.isDebugEnabled()) {
  514.                 LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
  515.                         Thread.currentThread(), s, dir);
  516.             }
  517.             Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  518.             try {
  519.                 probe.toFile().deleteOnExit();
  520.                 Files.createFile(probe);
  521.                 Duration fsResolution = getFsResolution(s, dir, probe);
  522.                 Duration clockResolution = measureClockResolution();
  523.                 fsResolution = fsResolution.plus(clockResolution);
  524.                 if (LOG.isDebugEnabled()) {
  525.                     LOG.debug(
  526.                             "{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$
  527.                             Thread.currentThread(), s, dir, fsResolution);
  528.                 }
  529.                 return Optional.of(fsResolution);
  530.             } catch (SecurityException e) {
  531.                 // Log it here; most likely deleteProbe() below will also run
  532.                 // into a SecurityException, and then this one will be lost
  533.                 // without trace.
  534.                 LOG.warn(e.getLocalizedMessage(), e);
  535.             } catch (AccessDeniedException e) {
  536.                 LOG.warn(e.getLocalizedMessage(), e); // see bug 548648
  537.             } catch (IOException e) {
  538.                 LOG.error(e.getLocalizedMessage(), e);
  539.             } finally {
  540.                 deleteProbe(probe);
  541.             }
  542.             return Optional.empty();
  543.         }

  544.         private static Duration getFsResolution(FileStore s, Path dir,
  545.                 Path probe) throws IOException {
  546.             File probeFile = probe.toFile();
  547.             FileTime t1 = Files.getLastModifiedTime(probe);
  548.             Instant t1i = t1.toInstant();
  549.             FileTime t2;
  550.             Duration last = FALLBACK_TIMESTAMP_RESOLUTION;
  551.             long minScale = MINIMUM_RESOLUTION_NANOS;
  552.             long scale = ONE_SECOND;
  553.             long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis());
  554.             long low = 0;
  555.             // Try up-front at microsecond and millisecond
  556.             long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND };
  557.             for (long interval : tries) {
  558.                 if (interval >= ONE_MILLISECOND) {
  559.                     probeFile.setLastModified(
  560.                             t1i.plusNanos(interval).toEpochMilli());
  561.                 } else {
  562.                     Files.setLastModifiedTime(probe,
  563.                             FileTime.from(t1i.plusNanos(interval)));
  564.                 }
  565.                 t2 = Files.getLastModifiedTime(probe);
  566.                 if (t2.compareTo(t1) > 0) {
  567.                     Duration diff = Duration.between(t1i, t2.toInstant());
  568.                     if (!diff.isZero() && !diff.isNegative()
  569.                             && diff.compareTo(last) < 0) {
  570.                         scale = interval;
  571.                         high = 1;
  572.                         last = diff;
  573.                         break;
  574.                     }
  575.                 } else {
  576.                     // Makes no sense going below
  577.                     minScale = Math.max(minScale, interval);
  578.                 }
  579.             }
  580.             // Binary search loop
  581.             while (high > low) {
  582.                 long mid = (high + low) / 2;
  583.                 if (mid == 0) {
  584.                     // Smaller than current scale. Adjust scale.
  585.                     long newScale = scale / 10;
  586.                     if (newScale < minScale) {
  587.                         break;
  588.                     }
  589.                     high *= scale / newScale;
  590.                     low *= scale / newScale;
  591.                     scale = newScale;
  592.                     mid = (high + low) / 2;
  593.                 }
  594.                 long delta = mid * scale;
  595.                 if (scale >= ONE_MILLISECOND) {
  596.                     probeFile.setLastModified(
  597.                             t1i.plusNanos(delta).toEpochMilli());
  598.                 } else {
  599.                     Files.setLastModifiedTime(probe,
  600.                             FileTime.from(t1i.plusNanos(delta)));
  601.                 }
  602.                 t2 = Files.getLastModifiedTime(probe);
  603.                 int cmp = t2.compareTo(t1);
  604.                 if (cmp > 0) {
  605.                     high = mid;
  606.                     Duration diff = Duration.between(t1i, t2.toInstant());
  607.                     if (diff.isZero() || diff.isNegative()) {
  608.                         LOG.warn(JGitText.get().logInconsistentFiletimeDiff,
  609.                                 Thread.currentThread(), s, dir, t2, t1, diff,
  610.                                 last);
  611.                         break;
  612.                     } else if (diff.compareTo(last) > 0) {
  613.                         LOG.warn(JGitText.get().logLargerFiletimeDiff,
  614.                                 Thread.currentThread(), s, dir, diff, last);
  615.                         break;
  616.                     }
  617.                     last = diff;
  618.                 } else if (cmp < 0) {
  619.                     LOG.warn(JGitText.get().logSmallerFiletime,
  620.                             Thread.currentThread(), s, dir, t2, t1, last);
  621.                     break;
  622.                 } else {
  623.                     // No discernible difference
  624.                     low = mid + 1;
  625.                 }
  626.             }
  627.             return last;
  628.         }

  629.         private static Duration measureClockResolution() {
  630.             Duration clockResolution = Duration.ZERO;
  631.             for (int i = 0; i < 10; i++) {
  632.                 Instant t1 = Instant.now();
  633.                 Instant t2 = t1;
  634.                 while (t2.compareTo(t1) <= 0) {
  635.                     t2 = Instant.now();
  636.                 }
  637.                 Duration r = Duration.between(t1, t2);
  638.                 if (r.compareTo(clockResolution) > 0) {
  639.                     clockResolution = r;
  640.                 }
  641.             }
  642.             return clockResolution;
  643.         }

  644.         private static void deleteProbe(Path probe) {
  645.             try {
  646.                 FileUtils.delete(probe.toFile(),
  647.                         FileUtils.SKIP_MISSING | FileUtils.RETRY);
  648.             } catch (IOException e) {
  649.                 LOG.error(e.getMessage(), e);
  650.             }
  651.         }

  652.         private static Optional<FileStoreAttributes> readFromConfig(
  653.                 FileStore s) {
  654.             StoredConfig userConfig;
  655.             try {
  656.                 userConfig = SystemReader.getInstance().getUserConfig();
  657.             } catch (IOException | ConfigInvalidException e) {
  658.                 LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
  659.                 return Optional.empty();
  660.             }
  661.             String key = getConfigKey(s);
  662.             Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
  663.                     ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  664.                     ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  665.                     UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  666.             if (UNDEFINED_DURATION.equals(resolution)) {
  667.                 return Optional.empty();
  668.             }
  669.             Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
  670.                     ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  671.                     ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  672.                     UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  673.             FileStoreAttributes c = new FileStoreAttributes(resolution);
  674.             if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
  675.                 c.minimalRacyInterval = minRacyThreshold;
  676.             }
  677.             return Optional.of(c);
  678.         }

  679.         private static void saveToConfig(FileStore s,
  680.                 FileStoreAttributes c) {
  681.             StoredConfig jgitConfig;
  682.             try {
  683.                 jgitConfig = SystemReader.getInstance().getJGitConfig();
  684.             } catch (IOException | ConfigInvalidException e) {
  685.                 LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
  686.                 return;
  687.             }
  688.             long resolution = c.getFsTimestampResolution().toNanos();
  689.             TimeUnit resolutionUnit = getUnit(resolution);
  690.             long resolutionValue = resolutionUnit.convert(resolution,
  691.                     TimeUnit.NANOSECONDS);

  692.             long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
  693.             TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
  694.             long minRacyThresholdValue = minRacyThresholdUnit
  695.                     .convert(minRacyThreshold, TimeUnit.NANOSECONDS);

  696.             final int max_retries = 5;
  697.             int retries = 0;
  698.             boolean succeeded = false;
  699.             String key = getConfigKey(s);
  700.             while (!succeeded && retries < max_retries) {
  701.                 try {
  702.                     jgitConfig.setString(
  703.                             ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  704.                             ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  705.                             String.format("%d %s", //$NON-NLS-1$
  706.                                     Long.valueOf(resolutionValue),
  707.                                     resolutionUnit.name().toLowerCase()));
  708.                     jgitConfig.setString(
  709.                             ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  710.                             ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  711.                             String.format("%d %s", //$NON-NLS-1$
  712.                                     Long.valueOf(minRacyThresholdValue),
  713.                                     minRacyThresholdUnit.name().toLowerCase()));
  714.                     jgitConfig.save();
  715.                     succeeded = true;
  716.                 } catch (LockFailedException e) {
  717.                     // race with another thread, wait a bit and try again
  718.                     try {
  719.                         retries++;
  720.                         if (retries < max_retries) {
  721.                             Thread.sleep(100);
  722.                             LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$
  723.                                     jgitConfig, Integer.valueOf(retries),
  724.                                     Integer.valueOf(max_retries));
  725.                         } else {
  726.                             LOG.warn(MessageFormat.format(
  727.                                     JGitText.get().lockFailedRetry, jgitConfig,
  728.                                     Integer.valueOf(retries)));
  729.                         }
  730.                     } catch (InterruptedException e1) {
  731.                         Thread.currentThread().interrupt();
  732.                         break;
  733.                     }
  734.                 } catch (IOException e) {
  735.                     LOG.error(MessageFormat.format(
  736.                             JGitText.get().cannotSaveConfig, jgitConfig), e);
  737.                     break;
  738.                 }
  739.             }
  740.         }

  741.         private static String getConfigKey(FileStore s) {
  742.             String storeKey;
  743.             if (SystemReader.getInstance().isWindows()) {
  744.                 Object attribute = null;
  745.                 try {
  746.                     attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
  747.                 } catch (IOException ignored) {
  748.                     // ignore
  749.                 }
  750.                 if (attribute instanceof Integer) {
  751.                     storeKey = attribute.toString();
  752.                 } else {
  753.                     storeKey = s.name();
  754.                 }
  755.             } else {
  756.                 storeKey = s.name();
  757.             }
  758.             return JAVA_VERSION_PREFIX + storeKey;
  759.         }

  760.         private static TimeUnit getUnit(long nanos) {
  761.             TimeUnit unit;
  762.             if (nanos < 200_000L) {
  763.                 unit = TimeUnit.NANOSECONDS;
  764.             } else if (nanos < 200_000_000L) {
  765.                 unit = TimeUnit.MICROSECONDS;
  766.             } else {
  767.                 unit = TimeUnit.MILLISECONDS;
  768.             }
  769.             return unit;
  770.         }

  771.         private final @NonNull Duration fsTimestampResolution;

  772.         private Duration minimalRacyInterval;

  773.         /**
  774.          * @return the measured minimal interval after a file has been modified
  775.          *         in which we cannot rely on lastModified to detect
  776.          *         modifications
  777.          */
  778.         public Duration getMinimalRacyInterval() {
  779.             return minimalRacyInterval;
  780.         }

  781.         /**
  782.          * @return the measured filesystem timestamp resolution
  783.          */
  784.         @NonNull
  785.         public Duration getFsTimestampResolution() {
  786.             return fsTimestampResolution;
  787.         }

  788.         /**
  789.          * Construct a FileStoreAttributeCache entry for the given filesystem
  790.          * timestamp resolution
  791.          *
  792.          * @param fsTimestampResolution
  793.          */
  794.         public FileStoreAttributes(
  795.                 @NonNull Duration fsTimestampResolution) {
  796.             this.fsTimestampResolution = fsTimestampResolution;
  797.             this.minimalRacyInterval = Duration.ZERO;
  798.         }

  799.         @SuppressWarnings({ "nls", "boxing" })
  800.         @Override
  801.         public String toString() {
  802.             return String.format(
  803.                     "FileStoreAttributes[fsTimestampResolution=%,d µs, "
  804.                             + "minimalRacyInterval=%,d µs]",
  805.                     fsTimestampResolution.toNanos() / 1000,
  806.                     minimalRacyInterval.toNanos() / 1000);
  807.         }

  808.     }

  809.     /** The auto-detected implementation selected for this operating system and JRE. */
  810.     public static final FS DETECTED = detect();

  811.     private static volatile FSFactory factory;

  812.     /**
  813.      * Auto-detect the appropriate file system abstraction.
  814.      *
  815.      * @return detected file system abstraction
  816.      */
  817.     public static FS detect() {
  818.         return detect(null);
  819.     }

  820.     /**
  821.      * Whether FileStore attributes should be determined asynchronously
  822.      *
  823.      * @param asynch
  824.      *            whether FileStore attributes should be determined
  825.      *            asynchronously. If false access to cached attributes may block
  826.      *            for some seconds for the first call per FileStore
  827.      * @since 5.1.9
  828.      * @deprecated Use {@link FileStoreAttributes#setBackground} instead
  829.      */
  830.     @Deprecated
  831.     public static void setAsyncFileStoreAttributes(boolean asynch) {
  832.         FileStoreAttributes.setBackground(asynch);
  833.     }

  834.     /**
  835.      * Auto-detect the appropriate file system abstraction, taking into account
  836.      * the presence of a Cygwin installation on the system. Using jgit in
  837.      * combination with Cygwin requires a more elaborate (and possibly slower)
  838.      * resolution of file system paths.
  839.      *
  840.      * @param cygwinUsed
  841.      *            <ul>
  842.      *            <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  843.      *            combination with jgit</li>
  844.      *            <li><code>Boolean.FALSE</code> to assume that Cygwin is
  845.      *            <b>not</b> used with jgit</li>
  846.      *            <li><code>null</code> to auto-detect whether a Cygwin
  847.      *            installation is present on the system and in this case assume
  848.      *            that Cygwin is used</li>
  849.      *            </ul>
  850.      *
  851.      *            Note: this parameter is only relevant on Windows.
  852.      * @return detected file system abstraction
  853.      */
  854.     public static FS detect(Boolean cygwinUsed) {
  855.         if (factory == null) {
  856.             factory = new FS.FSFactory();
  857.         }
  858.         return factory.detect(cygwinUsed);
  859.     }

  860.     /**
  861.      * Get cached FileStore attributes, if not yet available measure them using
  862.      * a probe file under the given directory.
  863.      *
  864.      * @param dir
  865.      *            the directory under which the probe file will be created to
  866.      *            measure the timer resolution.
  867.      * @return measured filesystem timestamp resolution
  868.      * @since 5.1.9
  869.      */
  870.     public static FileStoreAttributes getFileStoreAttributes(
  871.             @NonNull Path dir) {
  872.         return FileStoreAttributes.get(dir);
  873.     }

  874.     private volatile Holder<File> userHome;

  875.     private volatile Holder<File> gitSystemConfig;

  876.     /**
  877.      * Constructs a file system abstraction.
  878.      */
  879.     protected FS() {
  880.         // Do nothing by default.
  881.     }

  882.     /**
  883.      * Initialize this FS using another's current settings.
  884.      *
  885.      * @param src
  886.      *            the source FS to copy from.
  887.      */
  888.     protected FS(FS src) {
  889.         userHome = src.userHome;
  890.         gitSystemConfig = src.gitSystemConfig;
  891.     }

  892.     /**
  893.      * Create a new instance of the same type of FS.
  894.      *
  895.      * @return a new instance of the same type of FS.
  896.      */
  897.     public abstract FS newInstance();

  898.     /**
  899.      * Does this operating system and JRE support the execute flag on files?
  900.      *
  901.      * @return true if this implementation can provide reasonably accurate
  902.      *         executable bit information; false otherwise.
  903.      */
  904.     public abstract boolean supportsExecute();

  905.     /**
  906.      * Does this file system support atomic file creation via
  907.      * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
  908.      * not guaranteed that when two file system clients run createNewFile() in
  909.      * parallel only one will succeed. In such cases both clients may think they
  910.      * created a new file.
  911.      *
  912.      * @return true if this implementation support atomic creation of new Files
  913.      *         by {@link java.io.File#createNewFile()}
  914.      * @since 4.5
  915.      */
  916.     public boolean supportsAtomicCreateNewFile() {
  917.         return true;
  918.     }

  919.     /**
  920.      * Does this operating system and JRE supports symbolic links. The
  921.      * capability to handle symbolic links is detected at runtime.
  922.      *
  923.      * @return true if symbolic links may be used
  924.      * @since 3.0
  925.      */
  926.     public boolean supportsSymlinks() {
  927.         if (supportSymlinks == null) {
  928.             detectSymlinkSupport();
  929.         }
  930.         return Boolean.TRUE.equals(supportSymlinks);
  931.     }

  932.     private void detectSymlinkSupport() {
  933.         File tempFile = null;
  934.         try {
  935.             tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$
  936.             File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$
  937.             createSymLink(linkName, tempFile.getPath());
  938.             supportSymlinks = Boolean.TRUE;
  939.             linkName.delete();
  940.         } catch (IOException | UnsupportedOperationException | SecurityException
  941.                 | InternalError e) {
  942.             supportSymlinks = Boolean.FALSE;
  943.         } finally {
  944.             if (tempFile != null) {
  945.                 try {
  946.                     FileUtils.delete(tempFile);
  947.                 } catch (IOException e) {
  948.                     LOG.error(JGitText.get().cannotDeleteFile, tempFile);
  949.                 }
  950.             }
  951.         }
  952.     }

  953.     /**
  954.      * Is this file system case sensitive
  955.      *
  956.      * @return true if this implementation is case sensitive
  957.      */
  958.     public abstract boolean isCaseSensitive();

  959.     /**
  960.      * Determine if the file is executable (or not).
  961.      * <p>
  962.      * Not all platforms and JREs support executable flags on files. If the
  963.      * feature is unsupported this method will always return false.
  964.      * <p>
  965.      * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  966.      * this method returns false, rather than the state of the executable flags
  967.      * on the target file.</em>
  968.      *
  969.      * @param f
  970.      *            abstract path to test.
  971.      * @return true if the file is believed to be executable by the user.
  972.      */
  973.     public abstract boolean canExecute(File f);

  974.     /**
  975.      * Set a file to be executable by the user.
  976.      * <p>
  977.      * Not all platforms and JREs support executable flags on files. If the
  978.      * feature is unsupported this method will always return false and no
  979.      * changes will be made to the file specified.
  980.      *
  981.      * @param f
  982.      *            path to modify the executable status of.
  983.      * @param canExec
  984.      *            true to enable execution; false to disable it.
  985.      * @return true if the change succeeded; false otherwise.
  986.      */
  987.     public abstract boolean setExecute(File f, boolean canExec);

  988.     /**
  989.      * Get the last modified time of a file system object. If the OS/JRE support
  990.      * symbolic links, the modification time of the link is returned, rather
  991.      * than that of the link target.
  992.      *
  993.      * @param f
  994.      *            a {@link java.io.File} object.
  995.      * @return last modified time of f
  996.      * @throws java.io.IOException
  997.      * @since 3.0
  998.      * @deprecated use {@link #lastModifiedInstant(Path)} instead
  999.      */
  1000.     @Deprecated
  1001.     public long lastModified(File f) throws IOException {
  1002.         return FileUtils.lastModified(f);
  1003.     }

  1004.     /**
  1005.      * Get the last modified time of a file system object. If the OS/JRE support
  1006.      * symbolic links, the modification time of the link is returned, rather
  1007.      * than that of the link target.
  1008.      *
  1009.      * @param p
  1010.      *            a {@link Path} object.
  1011.      * @return last modified time of p
  1012.      * @since 5.1.9
  1013.      */
  1014.     public Instant lastModifiedInstant(Path p) {
  1015.         return FileUtils.lastModifiedInstant(p);
  1016.     }

  1017.     /**
  1018.      * Get the last modified time of a file system object. If the OS/JRE support
  1019.      * symbolic links, the modification time of the link is returned, rather
  1020.      * than that of the link target.
  1021.      *
  1022.      * @param f
  1023.      *            a {@link File} object.
  1024.      * @return last modified time of p
  1025.      * @since 5.1.9
  1026.      */
  1027.     public Instant lastModifiedInstant(File f) {
  1028.         return FileUtils.lastModifiedInstant(f.toPath());
  1029.     }

  1030.     /**
  1031.      * Set the last modified time of a file system object.
  1032.      * <p>
  1033.      * For symlinks it sets the modified time of the link target.
  1034.      *
  1035.      * @param f
  1036.      *            a {@link java.io.File} object.
  1037.      * @param time
  1038.      *            last modified time
  1039.      * @throws java.io.IOException
  1040.      * @since 3.0
  1041.      * @deprecated use {@link #setLastModified(Path, Instant)} instead
  1042.      */
  1043.     @Deprecated
  1044.     public void setLastModified(File f, long time) throws IOException {
  1045.         FileUtils.setLastModified(f, time);
  1046.     }

  1047.     /**
  1048.      * Set the last modified time of a file system object.
  1049.      * <p>
  1050.      * For symlinks it sets the modified time of the link target.
  1051.      *
  1052.      * @param p
  1053.      *            a {@link Path} object.
  1054.      * @param time
  1055.      *            last modified time
  1056.      * @throws java.io.IOException
  1057.      * @since 5.1.9
  1058.      */
  1059.     public void setLastModified(Path p, Instant time) throws IOException {
  1060.         FileUtils.setLastModified(p, time);
  1061.     }

  1062.     /**
  1063.      * Get the length of a file or link, If the OS/JRE supports symbolic links
  1064.      * it's the length of the link, else the length of the target.
  1065.      *
  1066.      * @param path
  1067.      *            a {@link java.io.File} object.
  1068.      * @return length of a file
  1069.      * @throws java.io.IOException
  1070.      * @since 3.0
  1071.      */
  1072.     public long length(File path) throws IOException {
  1073.         return FileUtils.getLength(path);
  1074.     }

  1075.     /**
  1076.      * Delete a file. Throws an exception if delete fails.
  1077.      *
  1078.      * @param f
  1079.      *            a {@link java.io.File} object.
  1080.      * @throws java.io.IOException
  1081.      *             this may be a Java7 subclass with detailed information
  1082.      * @since 3.3
  1083.      */
  1084.     public void delete(File f) throws IOException {
  1085.         FileUtils.delete(f);
  1086.     }

  1087.     /**
  1088.      * Resolve this file to its actual path name that the JRE can use.
  1089.      * <p>
  1090.      * This method can be relatively expensive. Computing a translation may
  1091.      * require forking an external process per path name translated. Callers
  1092.      * should try to minimize the number of translations necessary by caching
  1093.      * the results.
  1094.      * <p>
  1095.      * Not all platforms and JREs require path name translation. Currently only
  1096.      * Cygwin on Win32 require translation for Cygwin based paths.
  1097.      *
  1098.      * @param dir
  1099.      *            directory relative to which the path name is.
  1100.      * @param name
  1101.      *            path name to translate.
  1102.      * @return the translated path. <code>new File(dir,name)</code> if this
  1103.      *         platform does not require path name translation.
  1104.      */
  1105.     public File resolve(File dir, String name) {
  1106.         File abspn = new File(name);
  1107.         if (abspn.isAbsolute())
  1108.             return abspn;
  1109.         return new File(dir, name);
  1110.     }

  1111.     /**
  1112.      * Determine the user's home directory (location where preferences are).
  1113.      * <p>
  1114.      * This method can be expensive on the first invocation if path name
  1115.      * translation is required. Subsequent invocations return a cached result.
  1116.      * <p>
  1117.      * Not all platforms and JREs require path name translation. Currently only
  1118.      * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  1119.      *
  1120.      * @return the user's home directory; null if the user does not have one.
  1121.      */
  1122.     public File userHome() {
  1123.         Holder<File> p = userHome;
  1124.         if (p == null) {
  1125.             p = new Holder<>(safeUserHomeImpl());
  1126.             userHome = p;
  1127.         }
  1128.         return p.value;
  1129.     }

  1130.     private File safeUserHomeImpl() {
  1131.         File home;
  1132.         try {
  1133.             home = userHomeImpl();
  1134.             if (home != null) {
  1135.                 home.toPath();
  1136.                 return home;
  1137.             }
  1138.         } catch (RuntimeException e) {
  1139.             LOG.error(JGitText.get().exceptionWhileFindingUserHome, e);
  1140.         }
  1141.         home = defaultUserHomeImpl();
  1142.         if (home != null) {
  1143.             try {
  1144.                 home.toPath();
  1145.                 return home;
  1146.             } catch (InvalidPathException e) {
  1147.                 LOG.error(MessageFormat
  1148.                         .format(JGitText.get().invalidHomeDirectory, home), e);
  1149.             }
  1150.         }
  1151.         return null;
  1152.     }

  1153.     /**
  1154.      * Set the user's home directory location.
  1155.      *
  1156.      * @param path
  1157.      *            the location of the user's preferences; null if there is no
  1158.      *            home directory for the current user.
  1159.      * @return {@code this}.
  1160.      */
  1161.     public FS setUserHome(File path) {
  1162.         userHome = new Holder<>(path);
  1163.         return this;
  1164.     }

  1165.     /**
  1166.      * Does this file system have problems with atomic renames?
  1167.      *
  1168.      * @return true if the caller should retry a failed rename of a lock file.
  1169.      */
  1170.     public abstract boolean retryFailedLockFileCommit();

  1171.     /**
  1172.      * Return all the attributes of a file, without following symbolic links.
  1173.      *
  1174.      * @param file
  1175.      * @return {@link BasicFileAttributes} of the file
  1176.      * @throws IOException in case of any I/O errors accessing the file
  1177.      *
  1178.      * @since 4.5.6
  1179.      */
  1180.     public BasicFileAttributes fileAttributes(File file) throws IOException {
  1181.         return FileUtils.fileAttributes(file);
  1182.     }

  1183.     /**
  1184.      * Determine the user's home directory (location where preferences are).
  1185.      *
  1186.      * @return the user's home directory; null if the user does not have one.
  1187.      */
  1188.     protected File userHomeImpl() {
  1189.         return defaultUserHomeImpl();
  1190.     }

  1191.     private File defaultUserHomeImpl() {
  1192.         String home = AccessController.doPrivileged(
  1193.                 (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
  1194.         );
  1195.         if (home == null || home.length() == 0)
  1196.             return null;
  1197.         return new File(home).getAbsoluteFile();
  1198.     }

  1199.     /**
  1200.      * Searches the given path to see if it contains one of the given files.
  1201.      * Returns the first it finds which is executable. Returns null if not found
  1202.      * or if path is null.
  1203.      *
  1204.      * @param path
  1205.      *            List of paths to search separated by File.pathSeparator
  1206.      * @param lookFor
  1207.      *            Files to search for in the given path
  1208.      * @return the first match found, or null
  1209.      * @since 3.0
  1210.      */
  1211.     protected static File searchPath(String path, String... lookFor) {
  1212.         if (path == null) {
  1213.             return null;
  1214.         }

  1215.         for (String p : path.split(File.pathSeparator)) {
  1216.             for (String command : lookFor) {
  1217.                 File file = new File(p, command);
  1218.                 try {
  1219.                     if (file.isFile() && file.canExecute()) {
  1220.                         return file.getAbsoluteFile();
  1221.                     }
  1222.                 } catch (SecurityException e) {
  1223.                     LOG.warn(MessageFormat.format(
  1224.                             JGitText.get().skipNotAccessiblePath,
  1225.                             file.getPath()));
  1226.                 }
  1227.             }
  1228.         }
  1229.         return null;
  1230.     }

  1231.     /**
  1232.      * Execute a command and return a single line of output as a String
  1233.      *
  1234.      * @param dir
  1235.      *            Working directory for the command
  1236.      * @param command
  1237.      *            as component array
  1238.      * @param encoding
  1239.      *            to be used to parse the command's output
  1240.      * @return the one-line output of the command or {@code null} if there is
  1241.      *         none
  1242.      * @throws org.eclipse.jgit.errors.CommandFailedException
  1243.      *             thrown when the command failed (return code was non-zero)
  1244.      */
  1245.     @Nullable
  1246.     protected static String readPipe(File dir, String[] command,
  1247.             String encoding) throws CommandFailedException {
  1248.         return readPipe(dir, command, encoding, null);
  1249.     }

  1250.     /**
  1251.      * Execute a command and return a single line of output as a String
  1252.      *
  1253.      * @param dir
  1254.      *            Working directory for the command
  1255.      * @param command
  1256.      *            as component array
  1257.      * @param encoding
  1258.      *            to be used to parse the command's output
  1259.      * @param env
  1260.      *            Map of environment variables to be merged with those of the
  1261.      *            current process
  1262.      * @return the one-line output of the command or {@code null} if there is
  1263.      *         none
  1264.      * @throws org.eclipse.jgit.errors.CommandFailedException
  1265.      *             thrown when the command failed (return code was non-zero)
  1266.      * @since 4.0
  1267.      */
  1268.     @Nullable
  1269.     protected static String readPipe(File dir, String[] command,
  1270.             String encoding, Map<String, String> env)
  1271.             throws CommandFailedException {
  1272.         boolean debug = LOG.isDebugEnabled();
  1273.         try {
  1274.             if (debug) {
  1275.                 LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  1276.                         + dir);
  1277.             }
  1278.             ProcessBuilder pb = new ProcessBuilder(command);
  1279.             pb.directory(dir);
  1280.             if (env != null) {
  1281.                 pb.environment().putAll(env);
  1282.             }
  1283.             Process p;
  1284.             try {
  1285.                 p = pb.start();
  1286.             } catch (IOException e) {
  1287.                 // Process failed to start
  1288.                 throw new CommandFailedException(-1, e.getMessage(), e);
  1289.             }
  1290.             p.getOutputStream().close();
  1291.             GobblerThread gobbler = new GobblerThread(p, command, dir);
  1292.             gobbler.start();
  1293.             String r = null;
  1294.             try (BufferedReader lineRead = new BufferedReader(
  1295.                     new InputStreamReader(p.getInputStream(), encoding))) {
  1296.                 r = lineRead.readLine();
  1297.                 if (debug) {
  1298.                     LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  1299.                     LOG.debug("remaining output:\n"); //$NON-NLS-1$
  1300.                     String l;
  1301.                     while ((l = lineRead.readLine()) != null) {
  1302.                         LOG.debug(l);
  1303.                     }
  1304.                 }
  1305.             }

  1306.             for (;;) {
  1307.                 try {
  1308.                     int rc = p.waitFor();
  1309.                     gobbler.join();
  1310.                     if (rc == 0 && !gobbler.fail.get()) {
  1311.                         return r;
  1312.                     }
  1313.                     if (debug) {
  1314.                         LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  1315.                     }
  1316.                     throw new CommandFailedException(rc,
  1317.                             gobbler.errorMessage.get(),
  1318.                             gobbler.exception.get());
  1319.                 } catch (InterruptedException ie) {
  1320.                     // Stop bothering me, I have a zombie to reap.
  1321.                 }
  1322.             }
  1323.         } catch (IOException e) {
  1324.             LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  1325.         } catch (AccessControlException e) {
  1326.             LOG.warn(MessageFormat.format(
  1327.                     JGitText.get().readPipeIsNotAllowedRequiredPermission,
  1328.                     command, dir, e.getPermission()));
  1329.         } catch (SecurityException e) {
  1330.             LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
  1331.                     command, dir));
  1332.         }
  1333.         if (debug) {
  1334.             LOG.debug("readpipe returns null"); //$NON-NLS-1$
  1335.         }
  1336.         return null;
  1337.     }

  1338.     private static class GobblerThread extends Thread {

  1339.         /* The process has 5 seconds to exit after closing stderr */
  1340.         private static final int PROCESS_EXIT_TIMEOUT = 5;

  1341.         private final Process p;
  1342.         private final String desc;
  1343.         private final String dir;
  1344.         final AtomicBoolean fail = new AtomicBoolean();
  1345.         final AtomicReference<String> errorMessage = new AtomicReference<>();
  1346.         final AtomicReference<Throwable> exception = new AtomicReference<>();

  1347.         GobblerThread(Process p, String[] command, File dir) {
  1348.             this.p = p;
  1349.             this.desc = Arrays.toString(command);
  1350.             this.dir = Objects.toString(dir);
  1351.         }

  1352.         @Override
  1353.         public void run() {
  1354.             StringBuilder err = new StringBuilder();
  1355.             try (InputStream is = p.getErrorStream()) {
  1356.                 int ch;
  1357.                 while ((ch = is.read()) != -1) {
  1358.                     err.append((char) ch);
  1359.                 }
  1360.             } catch (IOException e) {
  1361.                 if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  1362.                     setError(e, e.getMessage(), p.exitValue());
  1363.                     fail.set(true);
  1364.                 } else {
  1365.                     // ignore. command terminated faster and stream was just closed
  1366.                     // or the process didn't terminate within timeout
  1367.                 }
  1368.             } finally {
  1369.                 if (waitForProcessCompletion(null) && err.length() > 0) {
  1370.                     setError(null, err.toString(), p.exitValue());
  1371.                     if (p.exitValue() != 0) {
  1372.                         fail.set(true);
  1373.                     }
  1374.                 }
  1375.             }
  1376.         }

  1377.         @SuppressWarnings("boxing")
  1378.         private boolean waitForProcessCompletion(IOException originalError) {
  1379.             try {
  1380.                 if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  1381.                     setError(originalError, MessageFormat.format(
  1382.                             JGitText.get().commandClosedStderrButDidntExit,
  1383.                             desc, PROCESS_EXIT_TIMEOUT), -1);
  1384.                     fail.set(true);
  1385.                     return false;
  1386.                 }
  1387.             } catch (InterruptedException e) {
  1388.                 setError(originalError, MessageFormat.format(
  1389.                         JGitText.get().threadInterruptedWhileRunning, desc), -1);
  1390.                 fail.set(true);
  1391.                 return false;
  1392.             }
  1393.             return true;
  1394.         }

  1395.         private void setError(IOException e, String message, int exitCode) {
  1396.             exception.set(e);
  1397.             errorMessage.set(MessageFormat.format(
  1398.                     JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  1399.                     desc, dir, Integer.valueOf(exitCode), message));
  1400.         }
  1401.     }

  1402.     /**
  1403.      * Discover the path to the Git executable.
  1404.      *
  1405.      * @return the path to the Git executable or {@code null} if it cannot be
  1406.      *         determined.
  1407.      * @since 4.0
  1408.      */
  1409.     protected abstract File discoverGitExe();

  1410.     /**
  1411.      * Discover the path to the system-wide Git configuration file
  1412.      *
  1413.      * @return the path to the system-wide Git configuration file or
  1414.      *         {@code null} if it cannot be determined.
  1415.      * @since 4.0
  1416.      */
  1417.     protected File discoverGitSystemConfig() {
  1418.         File gitExe = discoverGitExe();
  1419.         if (gitExe == null) {
  1420.             return null;
  1421.         }

  1422.         // Bug 480782: Check if the discovered git executable is JGit CLI
  1423.         String v;
  1424.         try {
  1425.             v = readPipe(gitExe.getParentFile(),
  1426.                     new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$
  1427.                     SystemReader.getInstance().getDefaultCharset().name());
  1428.         } catch (CommandFailedException e) {
  1429.             LOG.warn(e.getMessage());
  1430.             return null;
  1431.         }
  1432.         if (StringUtils.isEmptyOrNull(v)
  1433.                 || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  1434.             return null;
  1435.         }

  1436.         if (parseVersion(v) < makeVersion(2, 8, 0)) {
  1437.             // --show-origin was introduced in git 2.8.0. For older git: trick
  1438.             // it into printing the path to the config file by using "echo" as
  1439.             // the editor.
  1440.             Map<String, String> env = new HashMap<>();
  1441.             env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$

  1442.             String w;
  1443.             try {
  1444.                 // This command prints the path even if it doesn't exist
  1445.                 w = readPipe(gitExe.getParentFile(),
  1446.                         new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
  1447.                                 "--edit" }, //$NON-NLS-1$
  1448.                         SystemReader.getInstance().getDefaultCharset().name(),
  1449.                         env);
  1450.             } catch (CommandFailedException e) {
  1451.                 LOG.warn(e.getMessage());
  1452.                 return null;
  1453.             }
  1454.             if (StringUtils.isEmptyOrNull(w)) {
  1455.                 return null;
  1456.             }

  1457.             return new File(w);
  1458.         }
  1459.         String w;
  1460.         try {
  1461.             w = readPipe(gitExe.getParentFile(),
  1462.                     new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$
  1463.                             "--show-origin", "--list", "-z" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  1464.                     SystemReader.getInstance().getDefaultCharset().name());
  1465.         } catch (CommandFailedException e) {
  1466.             // This command fails if the system config doesn't exist
  1467.             if (LOG.isDebugEnabled()) {
  1468.                 LOG.debug(e.getMessage());
  1469.             }
  1470.             return null;
  1471.         }
  1472.         if (w == null) {
  1473.             return null;
  1474.         }
  1475.         // We get NUL-terminated items; the first one will be a file name,
  1476.         // prefixed by "file:". (Using -z is crucial, otherwise git quotes file
  1477.         // names with special characters.)
  1478.         int nul = w.indexOf(0);
  1479.         if (nul <= 0) {
  1480.             return null;
  1481.         }
  1482.         w = w.substring(0, nul);
  1483.         int colon = w.indexOf(':');
  1484.         if (colon < 0) {
  1485.             return null;
  1486.         }
  1487.         w = w.substring(colon + 1);
  1488.         return w.isEmpty() ? null : new File(w);
  1489.     }

  1490.     private long parseVersion(String version) {
  1491.         Matcher m = VERSION.matcher(version);
  1492.         if (m.find()) {
  1493.             try {
  1494.                 return makeVersion(
  1495.                         Integer.parseInt(m.group(1)),
  1496.                         Integer.parseInt(m.group(2)),
  1497.                         Integer.parseInt(m.group(3)));
  1498.             } catch (NumberFormatException e) {
  1499.                 // Ignore
  1500.             }
  1501.         }
  1502.         return -1;
  1503.     }

  1504.     private long makeVersion(int major, int minor, int patch) {
  1505.         return ((major * 10_000L) + minor) * 10_000L + patch;
  1506.     }

  1507.     /**
  1508.      * Get the currently used path to the system-wide Git configuration file.
  1509.      *
  1510.      * @return the currently used path to the system-wide Git configuration file
  1511.      *         or {@code null} if none has been set.
  1512.      * @since 4.0
  1513.      */
  1514.     public File getGitSystemConfig() {
  1515.         if (gitSystemConfig == null) {
  1516.             gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  1517.         }
  1518.         return gitSystemConfig.value;
  1519.     }

  1520.     /**
  1521.      * Set the path to the system-wide Git configuration file to use.
  1522.      *
  1523.      * @param configFile
  1524.      *            the path to the config file.
  1525.      * @return {@code this}
  1526.      * @since 4.0
  1527.      */
  1528.     public FS setGitSystemConfig(File configFile) {
  1529.         gitSystemConfig = new Holder<>(configFile);
  1530.         return this;
  1531.     }

  1532.     /**
  1533.      * Get the parent directory of this file's parent directory
  1534.      *
  1535.      * @param grandchild
  1536.      *            a {@link java.io.File} object.
  1537.      * @return the parent directory of this file's parent directory or
  1538.      *         {@code null} in case there's no grandparent directory
  1539.      * @since 4.0
  1540.      */
  1541.     protected static File resolveGrandparentFile(File grandchild) {
  1542.         if (grandchild != null) {
  1543.             File parent = grandchild.getParentFile();
  1544.             if (parent != null)
  1545.                 return parent.getParentFile();
  1546.         }
  1547.         return null;
  1548.     }

  1549.     /**
  1550.      * Check if a file is a symbolic link and read it
  1551.      *
  1552.      * @param path
  1553.      *            a {@link java.io.File} object.
  1554.      * @return target of link or null
  1555.      * @throws java.io.IOException
  1556.      * @since 3.0
  1557.      */
  1558.     public String readSymLink(File path) throws IOException {
  1559.         return FileUtils.readSymLink(path);
  1560.     }

  1561.     /**
  1562.      * Whether the path is a symbolic link (and we support these).
  1563.      *
  1564.      * @param path
  1565.      *            a {@link java.io.File} object.
  1566.      * @return true if the path is a symbolic link (and we support these)
  1567.      * @throws java.io.IOException
  1568.      * @since 3.0
  1569.      */
  1570.     public boolean isSymLink(File path) throws IOException {
  1571.         return FileUtils.isSymlink(path);
  1572.     }

  1573.     /**
  1574.      * Tests if the path exists, in case of a symbolic link, true even if the
  1575.      * target does not exist
  1576.      *
  1577.      * @param path
  1578.      *            a {@link java.io.File} object.
  1579.      * @return true if path exists
  1580.      * @since 3.0
  1581.      */
  1582.     public boolean exists(File path) {
  1583.         return FileUtils.exists(path);
  1584.     }

  1585.     /**
  1586.      * Check if path is a directory. If the OS/JRE supports symbolic links and
  1587.      * path is a symbolic link to a directory, this method returns false.
  1588.      *
  1589.      * @param path
  1590.      *            a {@link java.io.File} object.
  1591.      * @return true if file is a directory,
  1592.      * @since 3.0
  1593.      */
  1594.     public boolean isDirectory(File path) {
  1595.         return FileUtils.isDirectory(path);
  1596.     }

  1597.     /**
  1598.      * Examine if path represents a regular file. If the OS/JRE supports
  1599.      * symbolic links the test returns false if path represents a symbolic link.
  1600.      *
  1601.      * @param path
  1602.      *            a {@link java.io.File} object.
  1603.      * @return true if path represents a regular file
  1604.      * @since 3.0
  1605.      */
  1606.     public boolean isFile(File path) {
  1607.         return FileUtils.isFile(path);
  1608.     }

  1609.     /**
  1610.      * Whether path is hidden, either starts with . on unix or has the hidden
  1611.      * attribute in windows
  1612.      *
  1613.      * @param path
  1614.      *            a {@link java.io.File} object.
  1615.      * @return true if path is hidden, either starts with . on unix or has the
  1616.      *         hidden attribute in windows
  1617.      * @throws java.io.IOException
  1618.      * @since 3.0
  1619.      */
  1620.     public boolean isHidden(File path) throws IOException {
  1621.         return FileUtils.isHidden(path);
  1622.     }

  1623.     /**
  1624.      * Set the hidden attribute for file whose name starts with a period.
  1625.      *
  1626.      * @param path
  1627.      *            a {@link java.io.File} object.
  1628.      * @param hidden
  1629.      *            whether to set the file hidden
  1630.      * @throws java.io.IOException
  1631.      * @since 3.0
  1632.      */
  1633.     public void setHidden(File path, boolean hidden) throws IOException {
  1634.         FileUtils.setHidden(path, hidden);
  1635.     }

  1636.     /**
  1637.      * Create a symbolic link
  1638.      *
  1639.      * @param path
  1640.      *            a {@link java.io.File} object.
  1641.      * @param target
  1642.      *            target path of the symlink
  1643.      * @throws java.io.IOException
  1644.      * @since 3.0
  1645.      */
  1646.     public void createSymLink(File path, String target) throws IOException {
  1647.         FileUtils.createSymLink(path, target);
  1648.     }

  1649.     /**
  1650.      * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1651.      * of this class may take care to provide a safe implementation for this
  1652.      * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1653.      *
  1654.      * @param path
  1655.      *            the file to be created
  1656.      * @return <code>true</code> if the file was created, <code>false</code> if
  1657.      *         the file already existed
  1658.      * @throws java.io.IOException
  1659.      * @deprecated use {@link #createNewFileAtomic(File)} instead
  1660.      * @since 4.5
  1661.      */
  1662.     @Deprecated
  1663.     public boolean createNewFile(File path) throws IOException {
  1664.         return path.createNewFile();
  1665.     }

  1666.     /**
  1667.      * A token representing a file created by
  1668.      * {@link #createNewFileAtomic(File)}. The token must be retained until the
  1669.      * file has been deleted in order to guarantee that the unique file was
  1670.      * created atomically. As soon as the file is no longer needed the lock
  1671.      * token must be closed.
  1672.      *
  1673.      * @since 4.7
  1674.      */
  1675.     public static class LockToken implements Closeable {
  1676.         private boolean isCreated;

  1677.         private Optional<Path> link;

  1678.         LockToken(boolean isCreated, Optional<Path> link) {
  1679.             this.isCreated = isCreated;
  1680.             this.link = link;
  1681.         }

  1682.         /**
  1683.          * @return {@code true} if the file was created successfully
  1684.          */
  1685.         public boolean isCreated() {
  1686.             return isCreated;
  1687.         }

  1688.         @Override
  1689.         public void close() {
  1690.             if (!link.isPresent()) {
  1691.                 return;
  1692.             }
  1693.             Path p = link.get();
  1694.             if (!Files.exists(p)) {
  1695.                 return;
  1696.             }
  1697.             try {
  1698.                 Files.delete(p);
  1699.             } catch (IOException e) {
  1700.                 LOG.error(MessageFormat
  1701.                         .format(JGitText.get().closeLockTokenFailed, this), e);
  1702.             }
  1703.         }

  1704.         @Override
  1705.         public String toString() {
  1706.             return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
  1707.                     ", link=" //$NON-NLS-1$
  1708.                     + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
  1709.                             : "<null>]"); //$NON-NLS-1$
  1710.         }
  1711.     }

  1712.     /**
  1713.      * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1714.      * of this class may take care to provide a safe implementation for this
  1715.      * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1716.      *
  1717.      * @param path
  1718.      *            the file to be created
  1719.      * @return LockToken this token must be closed after the created file was
  1720.      *         deleted
  1721.      * @throws IOException
  1722.      * @since 4.7
  1723.      */
  1724.     public LockToken createNewFileAtomic(File path) throws IOException {
  1725.         return new LockToken(path.createNewFile(), Optional.empty());
  1726.     }

  1727.     /**
  1728.      * See
  1729.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  1730.      *
  1731.      * @param base
  1732.      *            The path against which <code>other</code> should be
  1733.      *            relativized.
  1734.      * @param other
  1735.      *            The path that will be made relative to <code>base</code>.
  1736.      * @return A relative path that, when resolved against <code>base</code>,
  1737.      *         will yield the original <code>other</code>.
  1738.      * @see FileUtils#relativizePath(String, String, String, boolean)
  1739.      * @since 3.7
  1740.      */
  1741.     public String relativize(String base, String other) {
  1742.         return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  1743.     }

  1744.     /**
  1745.      * Enumerates children of a directory.
  1746.      *
  1747.      * @param directory
  1748.      *            to get the children of
  1749.      * @param fileModeStrategy
  1750.      *            to use to calculate the git mode of a child
  1751.      * @return an array of entries for the children
  1752.      *
  1753.      * @since 5.0
  1754.      */
  1755.     public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
  1756.         File[] all = directory.listFiles();
  1757.         if (all == null) {
  1758.             return NO_ENTRIES;
  1759.         }
  1760.         Entry[] result = new Entry[all.length];
  1761.         for (int i = 0; i < result.length; i++) {
  1762.             result[i] = new FileEntry(all[i], this, fileModeStrategy);
  1763.         }
  1764.         return result;
  1765.     }

  1766.     /**
  1767.      * Checks whether the given hook is defined for the given repository, then
  1768.      * runs it with the given arguments.
  1769.      * <p>
  1770.      * The hook's standard output and error streams will be redirected to
  1771.      * <code>System.out</code> and <code>System.err</code> respectively. The
  1772.      * hook will have no stdin.
  1773.      * </p>
  1774.      *
  1775.      * @param repository
  1776.      *            The repository for which a hook should be run.
  1777.      * @param hookName
  1778.      *            The name of the hook to be executed.
  1779.      * @param args
  1780.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1781.      *            but can be an empty array.
  1782.      * @return The ProcessResult describing this hook's execution.
  1783.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1784.      *             if we fail to run the hook somehow. Causes may include an
  1785.      *             interrupted process or I/O errors.
  1786.      * @since 4.0
  1787.      */
  1788.     public ProcessResult runHookIfPresent(Repository repository,
  1789.             String hookName, String[] args) throws JGitInternalException {
  1790.         return runHookIfPresent(repository, hookName, args, System.out,
  1791.                 System.err, null);
  1792.     }

  1793.     /**
  1794.      * Checks whether the given hook is defined for the given repository, then
  1795.      * runs it with the given arguments.
  1796.      *
  1797.      * @param repository
  1798.      *            The repository for which a hook should be run.
  1799.      * @param hookName
  1800.      *            The name of the hook to be executed.
  1801.      * @param args
  1802.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1803.      *            but can be an empty array.
  1804.      * @param outRedirect
  1805.      *            A print stream on which to redirect the hook's stdout. Can be
  1806.      *            <code>null</code>, in which case the hook's standard output
  1807.      *            will be lost.
  1808.      * @param errRedirect
  1809.      *            A print stream on which to redirect the hook's stderr. Can be
  1810.      *            <code>null</code>, in which case the hook's standard error
  1811.      *            will be lost.
  1812.      * @param stdinArgs
  1813.      *            A string to pass on to the standard input of the hook. May be
  1814.      *            <code>null</code>.
  1815.      * @return The ProcessResult describing this hook's execution.
  1816.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1817.      *             if we fail to run the hook somehow. Causes may include an
  1818.      *             interrupted process or I/O errors.
  1819.      * @since 5.11
  1820.      */
  1821.     public ProcessResult runHookIfPresent(Repository repository,
  1822.             String hookName, String[] args, OutputStream outRedirect,
  1823.             OutputStream errRedirect, String stdinArgs)
  1824.             throws JGitInternalException {
  1825.         return new ProcessResult(Status.NOT_SUPPORTED);
  1826.     }

  1827.     /**
  1828.      * See
  1829.      * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, String)}
  1830.      * . Should only be called by FS supporting shell scripts execution.
  1831.      *
  1832.      * @param repository
  1833.      *            The repository for which a hook should be run.
  1834.      * @param hookName
  1835.      *            The name of the hook to be executed.
  1836.      * @param args
  1837.      *            Arguments to pass to this hook. Cannot be <code>null</code>,
  1838.      *            but can be an empty array.
  1839.      * @param outRedirect
  1840.      *            A print stream on which to redirect the hook's stdout. Can be
  1841.      *            <code>null</code>, in which case the hook's standard output
  1842.      *            will be lost.
  1843.      * @param errRedirect
  1844.      *            A print stream on which to redirect the hook's stderr. Can be
  1845.      *            <code>null</code>, in which case the hook's standard error
  1846.      *            will be lost.
  1847.      * @param stdinArgs
  1848.      *            A string to pass on to the standard input of the hook. May be
  1849.      *            <code>null</code>.
  1850.      * @return The ProcessResult describing this hook's execution.
  1851.      * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1852.      *             if we fail to run the hook somehow. Causes may include an
  1853.      *             interrupted process or I/O errors.
  1854.      * @since 5.11
  1855.      */
  1856.     protected ProcessResult internalRunHookIfPresent(Repository repository,
  1857.             String hookName, String[] args, OutputStream outRedirect,
  1858.             OutputStream errRedirect, String stdinArgs)
  1859.             throws JGitInternalException {
  1860.         File hookFile = findHook(repository, hookName);
  1861.         if (hookFile == null || hookName == null) {
  1862.             return new ProcessResult(Status.NOT_PRESENT);
  1863.         }

  1864.         File runDirectory = getRunDirectory(repository, hookName);
  1865.         if (runDirectory == null) {
  1866.             return new ProcessResult(Status.NOT_PRESENT);
  1867.         }
  1868.         String cmd = hookFile.getAbsolutePath();
  1869.         ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
  1870.         hookProcess.directory(runDirectory.getAbsoluteFile());
  1871.         Map<String, String> environment = hookProcess.environment();
  1872.         environment.put(Constants.GIT_DIR_KEY,
  1873.                 repository.getDirectory().getAbsolutePath());
  1874.         if (!repository.isBare()) {
  1875.             environment.put(Constants.GIT_WORK_TREE_KEY,
  1876.                     repository.getWorkTree().getAbsolutePath());
  1877.         }
  1878.         try {
  1879.             return new ProcessResult(runProcess(hookProcess, outRedirect,
  1880.                     errRedirect, stdinArgs), Status.OK);
  1881.         } catch (IOException e) {
  1882.             throw new JGitInternalException(MessageFormat.format(
  1883.                     JGitText.get().exceptionCaughtDuringExecutionOfHook,
  1884.                     hookName), e);
  1885.         } catch (InterruptedException e) {
  1886.             throw new JGitInternalException(MessageFormat.format(
  1887.                     JGitText.get().exceptionHookExecutionInterrupted,
  1888.                             hookName), e);
  1889.         }
  1890.     }

  1891.     /**
  1892.      * Quote a string (such as a file system path obtained from a Java
  1893.      * {@link File} or {@link Path} object) such that it can be passed as first
  1894.      * argument to {@link #runInShell(String, String[])}.
  1895.      * <p>
  1896.      * This default implementation returns the string unchanged.
  1897.      * </p>
  1898.      *
  1899.      * @param cmd
  1900.      *            the String to quote
  1901.      * @return the quoted string
  1902.      */
  1903.     String shellQuote(String cmd) {
  1904.         return cmd;
  1905.     }

  1906.     /**
  1907.      * Tries to find a hook matching the given one in the given repository.
  1908.      *
  1909.      * @param repository
  1910.      *            The repository within which to find a hook.
  1911.      * @param hookName
  1912.      *            The name of the hook we're trying to find.
  1913.      * @return The {@link java.io.File} containing this particular hook if it
  1914.      *         exists in the given repository, <code>null</code> otherwise.
  1915.      * @since 4.0
  1916.      */
  1917.     public File findHook(Repository repository, String hookName) {
  1918.         if (hookName == null) {
  1919.             return null;
  1920.         }
  1921.         File hookDir = getHooksDirectory(repository);
  1922.         if (hookDir == null) {
  1923.             return null;
  1924.         }
  1925.         File hookFile = new File(hookDir, hookName);
  1926.         if (hookFile.isAbsolute()) {
  1927.             if (!hookFile.exists() || (FS.DETECTED.supportsExecute()
  1928.                     && !FS.DETECTED.canExecute(hookFile))) {
  1929.                 return null;
  1930.             }
  1931.         } else {
  1932.             try {
  1933.                 File runDirectory = getRunDirectory(repository, hookName);
  1934.                 if (runDirectory == null) {
  1935.                     return null;
  1936.                 }
  1937.                 Path hookPath = runDirectory.getAbsoluteFile().toPath()
  1938.                         .resolve(hookFile.toPath());
  1939.                 FS fs = repository.getFS();
  1940.                 if (fs == null) {
  1941.                     fs = FS.DETECTED;
  1942.                 }
  1943.                 if (!Files.exists(hookPath) || (fs.supportsExecute()
  1944.                         && !fs.canExecute(hookPath.toFile()))) {
  1945.                     return null;
  1946.                 }
  1947.                 hookFile = hookPath.toFile();
  1948.             } catch (InvalidPathException e) {
  1949.                 LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath,
  1950.                         hookFile));
  1951.                 return null;
  1952.             }
  1953.         }
  1954.         return hookFile;
  1955.     }

  1956.     private File getRunDirectory(Repository repository,
  1957.             @NonNull String hookName) {
  1958.         if (repository.isBare()) {
  1959.             return repository.getDirectory();
  1960.         }
  1961.         switch (hookName) {
  1962.         case "pre-receive": //$NON-NLS-1$
  1963.         case "update": //$NON-NLS-1$
  1964.         case "post-receive": //$NON-NLS-1$
  1965.         case "post-update": //$NON-NLS-1$
  1966.         case "push-to-checkout": //$NON-NLS-1$
  1967.             return repository.getDirectory();
  1968.         default:
  1969.             return repository.getWorkTree();
  1970.         }
  1971.     }

  1972.     private File getHooksDirectory(Repository repository) {
  1973.         Config config = repository.getConfig();
  1974.         String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
  1975.                 null, ConfigConstants.CONFIG_KEY_HOOKS_PATH);
  1976.         if (hooksDir != null) {
  1977.             return new File(hooksDir);
  1978.         }
  1979.         File dir = repository.getDirectory();
  1980.         return dir == null ? null : new File(dir, Constants.HOOKS);
  1981.     }

  1982.     /**
  1983.      * Runs the given process until termination, clearing its stdout and stderr
  1984.      * streams on-the-fly.
  1985.      *
  1986.      * @param processBuilder
  1987.      *            The process builder configured for this process.
  1988.      * @param outRedirect
  1989.      *            A OutputStream on which to redirect the processes stdout. Can
  1990.      *            be <code>null</code>, in which case the processes standard
  1991.      *            output will be lost.
  1992.      * @param errRedirect
  1993.      *            A OutputStream on which to redirect the processes stderr. Can
  1994.      *            be <code>null</code>, in which case the processes standard
  1995.      *            error will be lost.
  1996.      * @param stdinArgs
  1997.      *            A string to pass on to the standard input of the hook. Can be
  1998.      *            <code>null</code>.
  1999.      * @return the exit value of this process.
  2000.      * @throws java.io.IOException
  2001.      *             if an I/O error occurs while executing this process.
  2002.      * @throws java.lang.InterruptedException
  2003.      *             if the current thread is interrupted while waiting for the
  2004.      *             process to end.
  2005.      * @since 4.2
  2006.      */
  2007.     public int runProcess(ProcessBuilder processBuilder,
  2008.             OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  2009.             throws IOException, InterruptedException {
  2010.         InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  2011.                         stdinArgs.getBytes(UTF_8));
  2012.         return runProcess(processBuilder, outRedirect, errRedirect, in);
  2013.     }

  2014.     /**
  2015.      * Runs the given process until termination, clearing its stdout and stderr
  2016.      * streams on-the-fly.
  2017.      *
  2018.      * @param processBuilder
  2019.      *            The process builder configured for this process.
  2020.      * @param outRedirect
  2021.      *            An OutputStream on which to redirect the processes stdout. Can
  2022.      *            be <code>null</code>, in which case the processes standard
  2023.      *            output will be lost.
  2024.      * @param errRedirect
  2025.      *            An OutputStream on which to redirect the processes stderr. Can
  2026.      *            be <code>null</code>, in which case the processes standard
  2027.      *            error will be lost.
  2028.      * @param inRedirect
  2029.      *            An InputStream from which to redirect the processes stdin. Can
  2030.      *            be <code>null</code>, in which case the process doesn't get
  2031.      *            any data over stdin. It is assumed that the whole InputStream
  2032.      *            will be consumed by the process. The method will close the
  2033.      *            inputstream after all bytes are read.
  2034.      * @return the return code of this process.
  2035.      * @throws java.io.IOException
  2036.      *             if an I/O error occurs while executing this process.
  2037.      * @throws java.lang.InterruptedException
  2038.      *             if the current thread is interrupted while waiting for the
  2039.      *             process to end.
  2040.      * @since 4.2
  2041.      */
  2042.     public int runProcess(ProcessBuilder processBuilder,
  2043.             OutputStream outRedirect, OutputStream errRedirect,
  2044.             InputStream inRedirect) throws IOException,
  2045.             InterruptedException {
  2046.         ExecutorService executor = Executors.newFixedThreadPool(2);
  2047.         Process process = null;
  2048.         // We'll record the first I/O exception that occurs, but keep on trying
  2049.         // to dispose of our open streams and file handles
  2050.         IOException ioException = null;
  2051.         try {
  2052.             process = processBuilder.start();
  2053.             executor.execute(
  2054.                     new StreamGobbler(process.getErrorStream(), errRedirect));
  2055.             executor.execute(
  2056.                     new StreamGobbler(process.getInputStream(), outRedirect));
  2057.             @SuppressWarnings("resource") // Closed in the finally block
  2058.             OutputStream outputStream = process.getOutputStream();
  2059.             try {
  2060.                 if (inRedirect != null) {
  2061.                     new StreamGobbler(inRedirect, outputStream).copy();
  2062.                 }
  2063.             } finally {
  2064.                 try {
  2065.                     outputStream.close();
  2066.                 } catch (IOException e) {
  2067.                     // When the process exits before consuming the input, the OutputStream
  2068.                     // is replaced with the null output stream. This null output stream
  2069.                     // throws IOException for all write calls. When StreamGobbler fails to
  2070.                     // flush the buffer because of this, this close call tries to flush it
  2071.                     // again. This causes another IOException. Since we ignore the
  2072.                     // IOException in StreamGobbler, we also ignore the exception here.
  2073.                 }
  2074.             }
  2075.             return process.waitFor();
  2076.         } catch (IOException e) {
  2077.             ioException = e;
  2078.         } finally {
  2079.             shutdownAndAwaitTermination(executor);
  2080.             if (process != null) {
  2081.                 try {
  2082.                     process.waitFor();
  2083.                 } catch (InterruptedException e) {
  2084.                     // Thrown by the outer try.
  2085.                     // Swallow this one to carry on our cleanup, and clear the
  2086.                     // interrupted flag (processes throw the exception without
  2087.                     // clearing the flag).
  2088.                     Thread.interrupted();
  2089.                 }
  2090.                 // A process doesn't clean its own resources even when destroyed
  2091.                 // Explicitly try and close all three streams, preserving the
  2092.                 // outer I/O exception if any.
  2093.                 if (inRedirect != null) {
  2094.                     inRedirect.close();
  2095.                 }
  2096.                 try {
  2097.                     process.getErrorStream().close();
  2098.                 } catch (IOException e) {
  2099.                     ioException = ioException != null ? ioException : e;
  2100.                 }
  2101.                 try {
  2102.                     process.getInputStream().close();
  2103.                 } catch (IOException e) {
  2104.                     ioException = ioException != null ? ioException : e;
  2105.                 }
  2106.                 try {
  2107.                     process.getOutputStream().close();
  2108.                 } catch (IOException e) {
  2109.                     ioException = ioException != null ? ioException : e;
  2110.                 }
  2111.                 process.destroy();
  2112.             }
  2113.         }
  2114.         // We can only be here if the outer try threw an IOException.
  2115.         throw ioException;
  2116.     }

  2117.     /**
  2118.      * Shuts down an {@link ExecutorService} in two phases, first by calling
  2119.      * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  2120.      * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  2121.      * necessary, to cancel any lingering tasks. Returns true if the pool has
  2122.      * been properly shutdown, false otherwise.
  2123.      * <p>
  2124.      *
  2125.      * @param pool
  2126.      *            the pool to shutdown
  2127.      * @return <code>true</code> if the pool has been properly shutdown,
  2128.      *         <code>false</code> otherwise.
  2129.      */
  2130.     private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  2131.         boolean hasShutdown = true;
  2132.         pool.shutdown(); // Disable new tasks from being submitted
  2133.         try {
  2134.             // Wait a while for existing tasks to terminate
  2135.             if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  2136.                 pool.shutdownNow(); // Cancel currently executing tasks
  2137.                 // Wait a while for tasks to respond to being canceled
  2138.                 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  2139.                     hasShutdown = false;
  2140.             }
  2141.         } catch (InterruptedException ie) {
  2142.             // (Re-)Cancel if current thread also interrupted
  2143.             pool.shutdownNow();
  2144.             // Preserve interrupt status
  2145.             Thread.currentThread().interrupt();
  2146.             hasShutdown = false;
  2147.         }
  2148.         return hasShutdown;
  2149.     }

  2150.     /**
  2151.      * Initialize a ProcessBuilder to run a command using the system shell.
  2152.      *
  2153.      * @param cmd
  2154.      *            command to execute. This string should originate from the
  2155.      *            end-user, and thus is platform specific.
  2156.      * @param args
  2157.      *            arguments to pass to command. These should be protected from
  2158.      *            shell evaluation.
  2159.      * @return a partially completed process builder. Caller should finish
  2160.      *         populating directory, environment, and then start the process.
  2161.      */
  2162.     public abstract ProcessBuilder runInShell(String cmd, String[] args);

  2163.     /**
  2164.      * Execute a command defined by a {@link java.lang.ProcessBuilder}.
  2165.      *
  2166.      * @param pb
  2167.      *            The command to be executed
  2168.      * @param in
  2169.      *            The standard input stream passed to the process
  2170.      * @return The result of the executed command
  2171.      * @throws java.lang.InterruptedException
  2172.      * @throws java.io.IOException
  2173.      * @since 4.2
  2174.      */
  2175.     public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  2176.             throws IOException, InterruptedException {
  2177.         try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  2178.                 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
  2179.                         1024 * 1024)) {
  2180.             int rc = runProcess(pb, stdout, stderr, in);
  2181.             return new ExecutionResult(stdout, stderr, rc);
  2182.         }
  2183.     }

  2184.     private static class Holder<V> {
  2185.         final V value;

  2186.         Holder(V value) {
  2187.             this.value = value;
  2188.         }
  2189.     }

  2190.     /**
  2191.      * File attributes we typically care for.
  2192.      *
  2193.      * @since 3.3
  2194.      */
  2195.     public static class Attributes {

  2196.         /**
  2197.          * @return true if this are the attributes of a directory
  2198.          */
  2199.         public boolean isDirectory() {
  2200.             return isDirectory;
  2201.         }

  2202.         /**
  2203.          * @return true if this are the attributes of an executable file
  2204.          */
  2205.         public boolean isExecutable() {
  2206.             return isExecutable;
  2207.         }

  2208.         /**
  2209.          * @return true if this are the attributes of a symbolic link
  2210.          */
  2211.         public boolean isSymbolicLink() {
  2212.             return isSymbolicLink;
  2213.         }

  2214.         /**
  2215.          * @return true if this are the attributes of a regular file
  2216.          */
  2217.         public boolean isRegularFile() {
  2218.             return isRegularFile;
  2219.         }

  2220.         /**
  2221.          * @return the time when the file was created
  2222.          */
  2223.         public long getCreationTime() {
  2224.             return creationTime;
  2225.         }

  2226.         /**
  2227.          * @return the time (milliseconds since 1970-01-01) when this object was
  2228.          *         last modified
  2229.          * @deprecated use getLastModifiedInstant instead
  2230.          */
  2231.         @Deprecated
  2232.         public long getLastModifiedTime() {
  2233.             return lastModifiedInstant.toEpochMilli();
  2234.         }

  2235.         /**
  2236.          * @return the time when this object was last modified
  2237.          * @since 5.1.9
  2238.          */
  2239.         public Instant getLastModifiedInstant() {
  2240.             return lastModifiedInstant;
  2241.         }

  2242.         private final boolean isDirectory;

  2243.         private final boolean isSymbolicLink;

  2244.         private final boolean isRegularFile;

  2245.         private final long creationTime;

  2246.         private final Instant lastModifiedInstant;

  2247.         private final boolean isExecutable;

  2248.         private final File file;

  2249.         private final boolean exists;

  2250.         /**
  2251.          * file length
  2252.          */
  2253.         protected long length = -1;

  2254.         final FS fs;

  2255.         Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  2256.                 boolean isExecutable, boolean isSymbolicLink,
  2257.                 boolean isRegularFile, long creationTime,
  2258.                 Instant lastModifiedInstant, long length) {
  2259.             this.fs = fs;
  2260.             this.file = file;
  2261.             this.exists = exists;
  2262.             this.isDirectory = isDirectory;
  2263.             this.isExecutable = isExecutable;
  2264.             this.isSymbolicLink = isSymbolicLink;
  2265.             this.isRegularFile = isRegularFile;
  2266.             this.creationTime = creationTime;
  2267.             this.lastModifiedInstant = lastModifiedInstant;
  2268.             this.length = length;
  2269.         }

  2270.         /**
  2271.          * Constructor when there are issues with reading. All attributes except
  2272.          * given will be set to the default values.
  2273.          *
  2274.          * @param fs
  2275.          * @param path
  2276.          */
  2277.         public Attributes(File path, FS fs) {
  2278.             this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
  2279.         }

  2280.         /**
  2281.          * @return length of this file object
  2282.          */
  2283.         public long getLength() {
  2284.             if (length == -1)
  2285.                 return length = file.length();
  2286.             return length;
  2287.         }

  2288.         /**
  2289.          * @return the filename
  2290.          */
  2291.         public String getName() {
  2292.             return file.getName();
  2293.         }

  2294.         /**
  2295.          * @return the file the attributes apply to
  2296.          */
  2297.         public File getFile() {
  2298.             return file;
  2299.         }

  2300.         boolean exists() {
  2301.             return exists;
  2302.         }
  2303.     }

  2304.     /**
  2305.      * Get the file attributes we care for.
  2306.      *
  2307.      * @param path
  2308.      *            a {@link java.io.File} object.
  2309.      * @return the file attributes we care for.
  2310.      * @since 3.3
  2311.      */
  2312.     public Attributes getAttributes(File path) {
  2313.         boolean isDirectory = isDirectory(path);
  2314.         boolean isFile = !isDirectory && path.isFile();
  2315.         assert path.exists() == isDirectory || isFile;
  2316.         boolean exists = isDirectory || isFile;
  2317.         boolean canExecute = exists && !isDirectory && canExecute(path);
  2318.         boolean isSymlink = false;
  2319.         Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
  2320.         long createTime = 0L;
  2321.         return new Attributes(this, path, exists, isDirectory, canExecute,
  2322.                 isSymlink, isFile, createTime, lastModified, -1);
  2323.     }

  2324.     /**
  2325.      * Normalize the unicode path to composed form.
  2326.      *
  2327.      * @param file
  2328.      *            a {@link java.io.File} object.
  2329.      * @return NFC-format File
  2330.      * @since 3.3
  2331.      */
  2332.     public File normalize(File file) {
  2333.         return file;
  2334.     }

  2335.     /**
  2336.      * Normalize the unicode path to composed form.
  2337.      *
  2338.      * @param name
  2339.      *            path name
  2340.      * @return NFC-format string
  2341.      * @since 3.3
  2342.      */
  2343.     public String normalize(String name) {
  2344.         return name;
  2345.     }

  2346.     /**
  2347.      * This runnable will consume an input stream's content into an output
  2348.      * stream as soon as it gets available.
  2349.      * <p>
  2350.      * Typically used to empty processes' standard output and error, preventing
  2351.      * them to choke.
  2352.      * </p>
  2353.      * <p>
  2354.      * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  2355.      * streams.
  2356.      * </p>
  2357.      */
  2358.     private static class StreamGobbler implements Runnable {
  2359.         private InputStream in;

  2360.         private OutputStream out;

  2361.         public StreamGobbler(InputStream stream, OutputStream output) {
  2362.             this.in = stream;
  2363.             this.out = output;
  2364.         }

  2365.         @Override
  2366.         public void run() {
  2367.             try {
  2368.                 copy();
  2369.             } catch (IOException e) {
  2370.                 // Do nothing on read failure; leave streams open.
  2371.             }
  2372.         }

  2373.         void copy() throws IOException {
  2374.             boolean writeFailure = false;
  2375.             byte[] buffer = new byte[4096];
  2376.             int readBytes;
  2377.             while ((readBytes = in.read(buffer)) != -1) {
  2378.                 // Do not try to write again after a failure, but keep
  2379.                 // reading as long as possible to prevent the input stream
  2380.                 // from choking.
  2381.                 if (!writeFailure && out != null) {
  2382.                     try {
  2383.                         out.write(buffer, 0, readBytes);
  2384.                         out.flush();
  2385.                     } catch (IOException e) {
  2386.                         writeFailure = true;
  2387.                     }
  2388.                 }
  2389.             }
  2390.         }
  2391.     }
  2392. }