FileUtils.java

  1. /*
  2.  * Copyright (C) 2010, Google Inc.
  3.  * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
  4.  * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.util;

  13. import static java.nio.charset.StandardCharsets.UTF_8;

  14. import java.io.File;
  15. import java.io.FileNotFoundException;
  16. import java.io.IOException;
  17. import java.io.InterruptedIOException;
  18. import java.nio.channels.FileChannel;
  19. import java.nio.file.AtomicMoveNotSupportedException;
  20. import java.nio.file.CopyOption;
  21. import java.nio.file.DirectoryNotEmptyException;
  22. import java.nio.file.Files;
  23. import java.nio.file.InvalidPathException;
  24. import java.nio.file.LinkOption;
  25. import java.nio.file.NoSuchFileException;
  26. import java.nio.file.Path;
  27. import java.nio.file.StandardCopyOption;
  28. import java.nio.file.StandardOpenOption;
  29. import java.nio.file.attribute.BasicFileAttributeView;
  30. import java.nio.file.attribute.BasicFileAttributes;
  31. import java.nio.file.attribute.FileTime;
  32. import java.nio.file.attribute.PosixFileAttributeView;
  33. import java.nio.file.attribute.PosixFileAttributes;
  34. import java.nio.file.attribute.PosixFilePermission;
  35. import java.text.MessageFormat;
  36. import java.text.Normalizer;
  37. import java.text.Normalizer.Form;
  38. import java.time.Instant;
  39. import java.util.ArrayList;
  40. import java.util.List;
  41. import java.util.Locale;
  42. import java.util.Random;
  43. import java.util.regex.Pattern;
  44. import java.util.stream.Stream;

  45. import org.eclipse.jgit.internal.JGitText;
  46. import org.eclipse.jgit.lib.Constants;
  47. import org.eclipse.jgit.util.FS.Attributes;
  48. import org.slf4j.Logger;
  49. import org.slf4j.LoggerFactory;

  50. /**
  51.  * File Utilities
  52.  */
  53. public class FileUtils {
  54.     private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);

  55.     private static final Random RNG = new Random();

  56.     /**
  57.      * Option to delete given {@code File}
  58.      */
  59.     public static final int NONE = 0;

  60.     /**
  61.      * Option to recursively delete given {@code File}
  62.      */
  63.     public static final int RECURSIVE = 1;

  64.     /**
  65.      * Option to retry deletion if not successful
  66.      */
  67.     public static final int RETRY = 2;

  68.     /**
  69.      * Option to skip deletion if file doesn't exist
  70.      */
  71.     public static final int SKIP_MISSING = 4;

  72.     /**
  73.      * Option not to throw exceptions when a deletion finally doesn't succeed.
  74.      * @since 2.0
  75.      */
  76.     public static final int IGNORE_ERRORS = 8;

  77.     /**
  78.      * Option to only delete empty directories. This option can be combined with
  79.      * {@link #RECURSIVE}
  80.      *
  81.      * @since 3.0
  82.      */
  83.     public static final int EMPTY_DIRECTORIES_ONLY = 16;

  84.     /**
  85.      * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
  86.      *
  87.      * @param f
  88.      *            {@code File} to be converted to {@code Path}
  89.      * @return the path represented by the file
  90.      * @throws java.io.IOException
  91.      *             in case the path represented by the file is not valid (
  92.      *             {@link java.nio.file.InvalidPathException})
  93.      * @since 4.10
  94.      */
  95.     public static Path toPath(File f) throws IOException {
  96.         try {
  97.             return f.toPath();
  98.         } catch (InvalidPathException ex) {
  99.             throw new IOException(ex);
  100.         }
  101.     }

  102.     /**
  103.      * Delete file or empty folder
  104.      *
  105.      * @param f
  106.      *            {@code File} to be deleted
  107.      * @throws java.io.IOException
  108.      *             if deletion of {@code f} fails. This may occur if {@code f}
  109.      *             didn't exist when the method was called. This can therefore
  110.      *             cause java.io.IOExceptions during race conditions when
  111.      *             multiple concurrent threads all try to delete the same file.
  112.      */
  113.     public static void delete(File f) throws IOException {
  114.         delete(f, NONE);
  115.     }

  116.     /**
  117.      * Delete file or folder
  118.      *
  119.      * @param f
  120.      *            {@code File} to be deleted
  121.      * @param options
  122.      *            deletion options, {@code RECURSIVE} for recursive deletion of
  123.      *            a subtree, {@code RETRY} to retry when deletion failed.
  124.      *            Retrying may help if the underlying file system doesn't allow
  125.      *            deletion of files being read by another thread.
  126.      * @throws java.io.IOException
  127.      *             if deletion of {@code f} fails. This may occur if {@code f}
  128.      *             didn't exist when the method was called. This can therefore
  129.      *             cause java.io.IOExceptions during race conditions when
  130.      *             multiple concurrent threads all try to delete the same file.
  131.      *             This exception is not thrown when IGNORE_ERRORS is set.
  132.      */
  133.     public static void delete(File f, int options) throws IOException {
  134.         FS fs = FS.DETECTED;
  135.         if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
  136.             return;

  137.         if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
  138.             final File[] items = f.listFiles();
  139.             if (items != null) {
  140.                 List<File> files = new ArrayList<>();
  141.                 List<File> dirs = new ArrayList<>();
  142.                 for (File c : items)
  143.                     if (c.isFile())
  144.                         files.add(c);
  145.                     else
  146.                         dirs.add(c);
  147.                 // Try to delete files first, otherwise options
  148.                 // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
  149.                 // directories before aborting, depending on order.
  150.                 for (File file : files)
  151.                     delete(file, options);
  152.                 for (File d : dirs)
  153.                     delete(d, options);
  154.             }
  155.         }

  156.         boolean delete = false;
  157.         if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
  158.             if (f.isDirectory()) {
  159.                 delete = true;
  160.             } else if ((options & IGNORE_ERRORS) == 0) {
  161.                 throw new IOException(MessageFormat.format(
  162.                         JGitText.get().deleteFileFailed, f.getAbsolutePath()));
  163.             }
  164.         } else {
  165.             delete = true;
  166.         }

  167.         if (delete) {
  168.             IOException t = null;
  169.             Path p = f.toPath();
  170.             boolean tryAgain;
  171.             do {
  172.                 tryAgain = false;
  173.                 try {
  174.                     Files.delete(p);
  175.                     return;
  176.                 } catch (NoSuchFileException | FileNotFoundException e) {
  177.                     handleDeleteException(f, e, options,
  178.                             SKIP_MISSING | IGNORE_ERRORS);
  179.                     return;
  180.                 } catch (DirectoryNotEmptyException e) {
  181.                     handleDeleteException(f, e, options, IGNORE_ERRORS);
  182.                     return;
  183.                 } catch (IOException e) {
  184.                     if (!f.canWrite()) {
  185.                         tryAgain = f.setWritable(true);
  186.                     }
  187.                     if (!tryAgain) {
  188.                         t = e;
  189.                     }
  190.                 }
  191.             } while (tryAgain);

  192.             if ((options & RETRY) != 0) {
  193.                 for (int i = 1; i < 10; i++) {
  194.                     try {
  195.                         Thread.sleep(100);
  196.                     } catch (InterruptedException ex) {
  197.                         // ignore
  198.                     }
  199.                     try {
  200.                         Files.deleteIfExists(p);
  201.                         return;
  202.                     } catch (IOException e) {
  203.                         t = e;
  204.                     }
  205.                 }
  206.             }
  207.             handleDeleteException(f, t, options, IGNORE_ERRORS);
  208.         }
  209.     }

  210.     private static void handleDeleteException(File f, IOException e,
  211.             int allOptions, int checkOptions) throws IOException {
  212.         if (e != null && (allOptions & checkOptions) == 0) {
  213.             throw new IOException(MessageFormat.format(
  214.                     JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
  215.         }
  216.     }

  217.     /**
  218.      * Rename a file or folder. If the rename fails and if we are running on a
  219.      * filesystem where it makes sense to repeat a failing rename then repeat
  220.      * the rename operation up to 9 times with 100ms sleep time between two
  221.      * calls. Furthermore if the destination exists and is directory hierarchy
  222.      * with only directories in it, the whole directory hierarchy will be
  223.      * deleted. If the target represents a non-empty directory structure, empty
  224.      * subdirectories within that structure may or may not be deleted even if
  225.      * the method fails. Furthermore if the destination exists and is a file
  226.      * then the file will be deleted and then the rename is retried.
  227.      * <p>
  228.      * This operation is <em>not</em> atomic.
  229.      *
  230.      * @see FS#retryFailedLockFileCommit()
  231.      * @param src
  232.      *            the old {@code File}
  233.      * @param dst
  234.      *            the new {@code File}
  235.      * @throws java.io.IOException
  236.      *             if the rename has failed
  237.      * @since 3.0
  238.      */
  239.     public static void rename(File src, File dst)
  240.             throws IOException {
  241.         rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
  242.     }

  243.     /**
  244.      * Rename a file or folder using the passed
  245.      * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
  246.      * running on a filesystem where it makes sense to repeat a failing rename
  247.      * then repeat the rename operation up to 9 times with 100ms sleep time
  248.      * between two calls. Furthermore if the destination exists and is a
  249.      * directory hierarchy with only directories in it, the whole directory
  250.      * hierarchy will be deleted. If the target represents a non-empty directory
  251.      * structure, empty subdirectories within that structure may or may not be
  252.      * deleted even if the method fails. Furthermore if the destination exists
  253.      * and is a file then the file will be replaced if
  254.      * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
  255.      * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
  256.      * rename will be done atomically or fail with an
  257.      * {@link java.nio.file.AtomicMoveNotSupportedException}
  258.      *
  259.      * @param src
  260.      *            the old file
  261.      * @param dst
  262.      *            the new file
  263.      * @param options
  264.      *            options to pass to
  265.      *            {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
  266.      * @throws java.nio.file.AtomicMoveNotSupportedException
  267.      *             if file cannot be moved as an atomic file system operation
  268.      * @throws java.io.IOException
  269.      * @since 4.1
  270.      */
  271.     public static void rename(final File src, final File dst,
  272.             CopyOption... options)
  273.                     throws AtomicMoveNotSupportedException, IOException {
  274.         int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
  275.         while (--attempts >= 0) {
  276.             try {
  277.                 Files.move(toPath(src), toPath(dst), options);
  278.                 return;
  279.             } catch (AtomicMoveNotSupportedException e) {
  280.                 throw e;
  281.             } catch (IOException e) {
  282.                 try {
  283.                     if (!dst.delete()) {
  284.                         delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  285.                     }
  286.                     // On *nix there is no try, you do or do not
  287.                     Files.move(toPath(src), toPath(dst), options);
  288.                     return;
  289.                 } catch (IOException e2) {
  290.                     // ignore and continue retry
  291.                 }
  292.             }
  293.             try {
  294.                 Thread.sleep(100);
  295.             } catch (InterruptedException e) {
  296.                 throw new IOException(
  297.                         MessageFormat.format(JGitText.get().renameFileFailed,
  298.                                 src.getAbsolutePath(), dst.getAbsolutePath()),
  299.                         e);
  300.             }
  301.         }
  302.         throw new IOException(
  303.                 MessageFormat.format(JGitText.get().renameFileFailed,
  304.                         src.getAbsolutePath(), dst.getAbsolutePath()));
  305.     }

  306.     /**
  307.      * Creates the directory named by this abstract pathname.
  308.      *
  309.      * @param d
  310.      *            directory to be created
  311.      * @throws java.io.IOException
  312.      *             if creation of {@code d} fails. This may occur if {@code d}
  313.      *             did exist when the method was called. This can therefore
  314.      *             cause java.io.IOExceptions during race conditions when
  315.      *             multiple concurrent threads all try to create the same
  316.      *             directory.
  317.      */
  318.     public static void mkdir(File d)
  319.             throws IOException {
  320.         mkdir(d, false);
  321.     }

  322.     /**
  323.      * Creates the directory named by this abstract pathname.
  324.      *
  325.      * @param d
  326.      *            directory to be created
  327.      * @param skipExisting
  328.      *            if {@code true} skip creation of the given directory if it
  329.      *            already exists in the file system
  330.      * @throws java.io.IOException
  331.      *             if creation of {@code d} fails. This may occur if {@code d}
  332.      *             did exist when the method was called. This can therefore
  333.      *             cause java.io.IOExceptions during race conditions when
  334.      *             multiple concurrent threads all try to create the same
  335.      *             directory.
  336.      */
  337.     public static void mkdir(File d, boolean skipExisting)
  338.             throws IOException {
  339.         if (!d.mkdir()) {
  340.             if (skipExisting && d.isDirectory())
  341.                 return;
  342.             throw new IOException(MessageFormat.format(
  343.                     JGitText.get().mkDirFailed, d.getAbsolutePath()));
  344.         }
  345.     }

  346.     /**
  347.      * Creates the directory named by this abstract pathname, including any
  348.      * necessary but nonexistent parent directories. Note that if this operation
  349.      * fails it may have succeeded in creating some of the necessary parent
  350.      * directories.
  351.      *
  352.      * @param d
  353.      *            directory to be created
  354.      * @throws java.io.IOException
  355.      *             if creation of {@code d} fails. This may occur if {@code d}
  356.      *             did exist when the method was called. This can therefore
  357.      *             cause java.io.IOExceptions during race conditions when
  358.      *             multiple concurrent threads all try to create the same
  359.      *             directory.
  360.      */
  361.     public static void mkdirs(File d) throws IOException {
  362.         mkdirs(d, false);
  363.     }

  364.     /**
  365.      * Creates the directory named by this abstract pathname, including any
  366.      * necessary but nonexistent parent directories. Note that if this operation
  367.      * fails it may have succeeded in creating some of the necessary parent
  368.      * directories.
  369.      *
  370.      * @param d
  371.      *            directory to be created
  372.      * @param skipExisting
  373.      *            if {@code true} skip creation of the given directory if it
  374.      *            already exists in the file system
  375.      * @throws java.io.IOException
  376.      *             if creation of {@code d} fails. This may occur if {@code d}
  377.      *             did exist when the method was called. This can therefore
  378.      *             cause java.io.IOExceptions during race conditions when
  379.      *             multiple concurrent threads all try to create the same
  380.      *             directory.
  381.      */
  382.     public static void mkdirs(File d, boolean skipExisting)
  383.             throws IOException {
  384.         if (!d.mkdirs()) {
  385.             if (skipExisting && d.isDirectory())
  386.                 return;
  387.             throw new IOException(MessageFormat.format(
  388.                     JGitText.get().mkDirsFailed, d.getAbsolutePath()));
  389.         }
  390.     }

  391.     /**
  392.      * Atomically creates a new, empty file named by this abstract pathname if
  393.      * and only if a file with this name does not yet exist. The check for the
  394.      * existence of the file and the creation of the file if it does not exist
  395.      * are a single operation that is atomic with respect to all other
  396.      * filesystem activities that might affect the file.
  397.      * <p>
  398.      * Note: this method should not be used for file-locking, as the resulting
  399.      * protocol cannot be made to work reliably. The
  400.      * {@link java.nio.channels.FileLock} facility should be used instead.
  401.      *
  402.      * @param f
  403.      *            the file to be created
  404.      * @throws java.io.IOException
  405.      *             if the named file already exists or if an I/O error occurred
  406.      */
  407.     public static void createNewFile(File f) throws IOException {
  408.         if (!f.createNewFile())
  409.             throw new IOException(MessageFormat.format(
  410.                     JGitText.get().createNewFileFailed, f));
  411.     }

  412.     /**
  413.      * Create a symbolic link
  414.      *
  415.      * @param path
  416.      *            the path of the symbolic link to create
  417.      * @param target
  418.      *            the target of the symbolic link
  419.      * @return the path to the symbolic link
  420.      * @throws java.io.IOException
  421.      * @since 4.2
  422.      */
  423.     public static Path createSymLink(File path, String target)
  424.             throws IOException {
  425.         Path nioPath = toPath(path);
  426.         if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
  427.             BasicFileAttributes attrs = Files.readAttributes(nioPath,
  428.                     BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  429.             if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
  430.                 delete(path);
  431.             } else {
  432.                 delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
  433.             }
  434.         }
  435.         if (SystemReader.getInstance().isWindows()) {
  436.             target = target.replace('/', '\\');
  437.         }
  438.         Path nioTarget = toPath(new File(target));
  439.         return Files.createSymbolicLink(nioPath, nioTarget);
  440.     }

  441.     /**
  442.      * Read target path of the symlink.
  443.      *
  444.      * @param path
  445.      *            a {@link java.io.File} object.
  446.      * @return target path of the symlink, or null if it is not a symbolic link
  447.      * @throws java.io.IOException
  448.      * @since 3.0
  449.      */
  450.     public static String readSymLink(File path) throws IOException {
  451.         Path nioPath = toPath(path);
  452.         Path target = Files.readSymbolicLink(nioPath);
  453.         String targetString = target.toString();
  454.         if (SystemReader.getInstance().isWindows()) {
  455.             targetString = targetString.replace('\\', '/');
  456.         } else if (SystemReader.getInstance().isMacOS()) {
  457.             targetString = Normalizer.normalize(targetString, Form.NFC);
  458.         }
  459.         return targetString;
  460.     }

  461.     /**
  462.      * Create a temporary directory.
  463.      *
  464.      * @param prefix
  465.      *            prefix string
  466.      * @param suffix
  467.      *            suffix string
  468.      * @param dir
  469.      *            The parent dir, can be null to use system default temp dir.
  470.      * @return the temp dir created.
  471.      * @throws java.io.IOException
  472.      * @since 3.4
  473.      */
  474.     public static File createTempDir(String prefix, String suffix, File dir)
  475.             throws IOException {
  476.         final int RETRIES = 1; // When something bad happens, retry once.
  477.         for (int i = 0; i < RETRIES; i++) {
  478.             File tmp = File.createTempFile(prefix, suffix, dir);
  479.             if (!tmp.delete())
  480.                 continue;
  481.             if (!tmp.mkdir())
  482.                 continue;
  483.             return tmp;
  484.         }
  485.         throw new IOException(JGitText.get().cannotCreateTempDir);
  486.     }

  487.     /**
  488.      * Expresses <code>other</code> as a relative file path from
  489.      * <code>base</code>. File-separator and case sensitivity are based on the
  490.      * current file system.
  491.      *
  492.      * See also
  493.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  494.      *
  495.      * @param base
  496.      *            Base path
  497.      * @param other
  498.      *            Destination path
  499.      * @return Relative path from <code>base</code> to <code>other</code>
  500.      * @since 4.8
  501.      */
  502.     public static String relativizeNativePath(String base, String other) {
  503.         return FS.DETECTED.relativize(base, other);
  504.     }

  505.     /**
  506.      * Expresses <code>other</code> as a relative file path from
  507.      * <code>base</code>. File-separator and case sensitivity are based on Git's
  508.      * internal representation of files (which matches Unix).
  509.      *
  510.      * See also
  511.      * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  512.      *
  513.      * @param base
  514.      *            Base path
  515.      * @param other
  516.      *            Destination path
  517.      * @return Relative path from <code>base</code> to <code>other</code>
  518.      * @since 4.8
  519.      */
  520.     public static String relativizeGitPath(String base, String other) {
  521.         return relativizePath(base, other, "/", false); //$NON-NLS-1$
  522.     }


  523.     /**
  524.      * Expresses <code>other</code> as a relative file path from <code>base</code>
  525.      * <p>
  526.      * For example, if called with the two following paths :
  527.      *
  528.      * <pre>
  529.      * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
  530.      * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
  531.      * </pre>
  532.      *
  533.      * This will return "..\\another_project\\pom.xml".
  534.      *
  535.      * <p>
  536.      * <b>Note</b> that this will return the empty String if <code>base</code>
  537.      * and <code>other</code> are equal.
  538.      * </p>
  539.      *
  540.      * @param base
  541.      *            The path against which <code>other</code> should be
  542.      *            relativized. This will be assumed to denote the path to a
  543.      *            folder and not a file.
  544.      * @param other
  545.      *            The path that will be made relative to <code>base</code>.
  546.      * @param dirSeparator
  547.      *            A string that separates components of the path. In practice, this is "/" or "\\".
  548.      * @param caseSensitive
  549.      *            Whether to consider differently-cased directory names as distinct
  550.      * @return A relative path that, when resolved against <code>base</code>,
  551.      *         will yield the original <code>other</code>.
  552.      * @since 4.8
  553.      */
  554.     public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
  555.         if (base.equals(other))
  556.             return ""; //$NON-NLS-1$

  557.         final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
  558.         final String[] otherSegments = other.split(Pattern
  559.                 .quote(dirSeparator));

  560.         int commonPrefix = 0;
  561.         while (commonPrefix < baseSegments.length
  562.                 && commonPrefix < otherSegments.length) {
  563.             if (caseSensitive
  564.                     && baseSegments[commonPrefix]
  565.                     .equals(otherSegments[commonPrefix]))
  566.                 commonPrefix++;
  567.             else if (!caseSensitive
  568.                     && baseSegments[commonPrefix]
  569.                             .equalsIgnoreCase(otherSegments[commonPrefix]))
  570.                 commonPrefix++;
  571.             else
  572.                 break;
  573.         }

  574.         final StringBuilder builder = new StringBuilder();
  575.         for (int i = commonPrefix; i < baseSegments.length; i++)
  576.             builder.append("..").append(dirSeparator); //$NON-NLS-1$
  577.         for (int i = commonPrefix; i < otherSegments.length; i++) {
  578.             builder.append(otherSegments[i]);
  579.             if (i < otherSegments.length - 1)
  580.                 builder.append(dirSeparator);
  581.         }
  582.         return builder.toString();
  583.     }

  584.     /**
  585.      * Determine if an IOException is a Stale NFS File Handle
  586.      *
  587.      * @param ioe
  588.      *            an {@link java.io.IOException} object.
  589.      * @return a boolean true if the IOException is a Stale NFS FIle Handle
  590.      * @since 4.1
  591.      */
  592.     public static boolean isStaleFileHandle(IOException ioe) {
  593.         String msg = ioe.getMessage();
  594.         return msg != null
  595.                 && msg.toLowerCase(Locale.ROOT)
  596.                         .matches("stale .*file .*handle"); //$NON-NLS-1$
  597.     }

  598.     /**
  599.      * Determine if a throwable or a cause in its causal chain is a Stale NFS
  600.      * File Handle
  601.      *
  602.      * @param throwable
  603.      *            a {@link java.lang.Throwable} object.
  604.      * @return a boolean true if the throwable or a cause in its causal chain is
  605.      *         a Stale NFS File Handle
  606.      * @since 4.7
  607.      */
  608.     public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
  609.         while (throwable != null) {
  610.             if (throwable instanceof IOException
  611.                     && isStaleFileHandle((IOException) throwable)) {
  612.                 return true;
  613.             }
  614.             throwable = throwable.getCause();
  615.         }
  616.         return false;
  617.     }

  618.     /**
  619.      * Like a {@link java.util.function.Function} but throwing an
  620.      * {@link Exception}.
  621.      *
  622.      * @param <A>
  623.      *            input type
  624.      * @param <B>
  625.      *            output type
  626.      * @since 6.2
  627.      */
  628.     @FunctionalInterface
  629.     public interface IOFunction<A, B> {

  630.         /**
  631.          * Performs the function.
  632.          *
  633.          * @param t
  634.          *            input to operate on
  635.          * @return the output
  636.          * @throws Exception
  637.          *             if a problem occurs
  638.          */
  639.         B apply(A t) throws Exception;
  640.     }

  641.     private static void backOff(long delay, IOException cause)
  642.             throws IOException {
  643.         try {
  644.             Thread.sleep(delay);
  645.         } catch (InterruptedException e) {
  646.             IOException interruption = new InterruptedIOException();
  647.             interruption.initCause(e);
  648.             interruption.addSuppressed(cause);
  649.             Thread.currentThread().interrupt(); // Re-set flag
  650.             throw interruption;
  651.         }
  652.     }

  653.     /**
  654.      * Invokes the given {@link IOFunction}, performing a limited number of
  655.      * re-tries if exceptions occur that indicate either a stale NFS file handle
  656.      * or that indicate that the file may be written concurrently.
  657.      *
  658.      * @param <T>
  659.      *            result type
  660.      * @param file
  661.      *            to read
  662.      * @param reader
  663.      *            for reading the file and creating an instance of {@code T}
  664.      * @return the result of the {@code reader}, or {@code null} if the file
  665.      *         does not exist
  666.      * @throws Exception
  667.      *             if a problem occurs
  668.      * @since 6.2
  669.      */
  670.     public static <T> T readWithRetries(File file,
  671.             IOFunction<File, ? extends T> reader)
  672.             throws Exception {
  673.         int maxStaleRetries = 5;
  674.         int retries = 0;
  675.         long backoff = 50;
  676.         while (true) {
  677.             try {
  678.                 try {
  679.                     return reader.apply(file);
  680.                 } catch (IOException e) {
  681.                     if (FileUtils.isStaleFileHandleInCausalChain(e)
  682.                             && retries < maxStaleRetries) {
  683.                         if (LOG.isDebugEnabled()) {
  684.                             LOG.debug(MessageFormat.format(
  685.                                     JGitText.get().packedRefsHandleIsStale,
  686.                                     Integer.valueOf(retries)), e);
  687.                         }
  688.                         retries++;
  689.                         continue;
  690.                     }
  691.                     throw e;
  692.                 }
  693.             } catch (FileNotFoundException noFile) {
  694.                 if (!file.isFile()) {
  695.                     return null;
  696.                 }
  697.                 // Probably Windows and some other thread is writing the file
  698.                 // concurrently.
  699.                 if (backoff > 1000) {
  700.                     throw noFile;
  701.                 }
  702.                 backOff(backoff, noFile);
  703.                 backoff *= 2; // 50, 100, 200, 400, 800 ms
  704.             }
  705.         }
  706.     }

  707.     /**
  708.      * @param file
  709.      * @return {@code true} if the passed file is a symbolic link
  710.      */
  711.     static boolean isSymlink(File file) {
  712.         return Files.isSymbolicLink(file.toPath());
  713.     }

  714.     /**
  715.      * @param file
  716.      * @return lastModified attribute for given file, not following symbolic
  717.      *         links
  718.      * @throws IOException
  719.      * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
  720.      *             FileTime
  721.      */
  722.     @Deprecated
  723.     static long lastModified(File file) throws IOException {
  724.         return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
  725.                 .toMillis();
  726.     }

  727.     /**
  728.      * @param path
  729.      * @return lastModified attribute for given file, not following symbolic
  730.      *         links
  731.      */
  732.     static Instant lastModifiedInstant(Path path) {
  733.         try {
  734.             return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
  735.                     .toInstant();
  736.         } catch (NoSuchFileException e) {
  737.             LOG.debug(
  738.                     "Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
  739.                     path);
  740.             return Instant.EPOCH;
  741.         } catch (IOException e) {
  742.             LOG.error(MessageFormat
  743.                     .format(JGitText.get().readLastModifiedFailed, path), e);
  744.             return Instant.ofEpochMilli(path.toFile().lastModified());
  745.         }
  746.     }

  747.     /**
  748.      * Return all the attributes of a file, without following symbolic links.
  749.      *
  750.      * @param file
  751.      * @return {@link BasicFileAttributes} of the file
  752.      * @throws IOException in case of any I/O errors accessing the file
  753.      *
  754.      * @since 4.5.6
  755.      */
  756.     static BasicFileAttributes fileAttributes(File file) throws IOException {
  757.         return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
  758.     }

  759.     /**
  760.      * Set the last modified time of a file system object.
  761.      *
  762.      * @param file
  763.      * @param time
  764.      * @throws IOException
  765.      */
  766.     @Deprecated
  767.     static void setLastModified(File file, long time) throws IOException {
  768.         Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
  769.     }

  770.     /**
  771.      * Set the last modified time of a file system object.
  772.      *
  773.      * @param path
  774.      * @param time
  775.      * @throws IOException
  776.      */
  777.     static void setLastModified(Path path, Instant time)
  778.             throws IOException {
  779.         Files.setLastModifiedTime(path, FileTime.from(time));
  780.     }

  781.     /**
  782.      * @param file
  783.      * @return {@code true} if the given file exists, not following symbolic
  784.      *         links
  785.      */
  786.     static boolean exists(File file) {
  787.         return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  788.     }

  789.     /**
  790.      * @param file
  791.      * @return {@code true} if the given file is hidden
  792.      * @throws IOException
  793.      */
  794.     static boolean isHidden(File file) throws IOException {
  795.         return Files.isHidden(toPath(file));
  796.     }

  797.     /**
  798.      * Set a file hidden (on Windows)
  799.      *
  800.      * @param file
  801.      *            a {@link java.io.File} object.
  802.      * @param hidden
  803.      *            a boolean.
  804.      * @throws java.io.IOException
  805.      * @since 4.1
  806.      */
  807.     public static void setHidden(File file, boolean hidden) throws IOException {
  808.         Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
  809.                 LinkOption.NOFOLLOW_LINKS);
  810.     }

  811.     /**
  812.      * Get file length
  813.      *
  814.      * @param file
  815.      *            a {@link java.io.File}.
  816.      * @return length of the given file
  817.      * @throws java.io.IOException
  818.      * @since 4.1
  819.      */
  820.     public static long getLength(File file) throws IOException {
  821.         Path nioPath = toPath(file);
  822.         if (Files.isSymbolicLink(nioPath))
  823.             return Files.readSymbolicLink(nioPath).toString()
  824.                     .getBytes(UTF_8).length;
  825.         return Files.size(nioPath);
  826.     }

  827.     /**
  828.      * @param file
  829.      * @return {@code true} if the given file is a directory, not following
  830.      *         symbolic links
  831.      */
  832.     static boolean isDirectory(File file) {
  833.         return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  834.     }

  835.     /**
  836.      * @param file
  837.      * @return {@code true} if the given file is a file, not following symbolic
  838.      *         links
  839.      */
  840.     static boolean isFile(File file) {
  841.         return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
  842.     }

  843.     /**
  844.      * Whether the path is a directory with files in it.
  845.      *
  846.      * @param dir
  847.      *            directory path
  848.      * @return {@code true} if the given directory path contains files
  849.      * @throws IOException
  850.      *             on any I/O errors accessing the path
  851.      *
  852.      * @since 5.11
  853.      */
  854.     public static boolean hasFiles(Path dir) throws IOException {
  855.         try (Stream<Path> stream = Files.list(dir)) {
  856.             return stream.findAny().isPresent();
  857.         }
  858.     }

  859.     /**
  860.      * Whether the given file can be executed.
  861.      *
  862.      * @param file
  863.      *            a {@link java.io.File} object.
  864.      * @return {@code true} if the given file can be executed.
  865.      * @since 4.1
  866.      */
  867.     public static boolean canExecute(File file) {
  868.         if (!isFile(file)) {
  869.             return false;
  870.         }
  871.         return Files.isExecutable(file.toPath());
  872.     }

  873.     /**
  874.      * @param fs
  875.      * @param file
  876.      * @return non null attributes object
  877.      */
  878.     static Attributes getFileAttributesBasic(FS fs, File file) {
  879.         try {
  880.             Path nioPath = toPath(file);
  881.             BasicFileAttributes readAttributes = nioPath
  882.                     .getFileSystem()
  883.                     .provider()
  884.                     .getFileAttributeView(nioPath,
  885.                             BasicFileAttributeView.class,
  886.                             LinkOption.NOFOLLOW_LINKS).readAttributes();
  887.             Attributes attributes = new Attributes(fs, file,
  888.                     true,
  889.                     readAttributes.isDirectory(),
  890.                     fs.supportsExecute() ? file.canExecute() : false,
  891.                     readAttributes.isSymbolicLink(),
  892.                     readAttributes.isRegularFile(), //
  893.                     readAttributes.creationTime().toMillis(), //
  894.                     readAttributes.lastModifiedTime().toInstant(),
  895.                     readAttributes.isSymbolicLink() ? Constants
  896.                             .encode(readSymLink(file)).length
  897.                             : readAttributes.size());
  898.             return attributes;
  899.         } catch (IOException e) {
  900.             return new Attributes(file, fs);
  901.         }
  902.     }

  903.     /**
  904.      * Get file system attributes for the given file.
  905.      *
  906.      * @param fs
  907.      *            a {@link org.eclipse.jgit.util.FS} object.
  908.      * @param file
  909.      *            a {@link java.io.File}.
  910.      * @return file system attributes for the given file.
  911.      * @since 4.1
  912.      */
  913.     public static Attributes getFileAttributesPosix(FS fs, File file) {
  914.         try {
  915.             Path nioPath = toPath(file);
  916.             PosixFileAttributes readAttributes = nioPath
  917.                     .getFileSystem()
  918.                     .provider()
  919.                     .getFileAttributeView(nioPath,
  920.                             PosixFileAttributeView.class,
  921.                             LinkOption.NOFOLLOW_LINKS).readAttributes();
  922.             Attributes attributes = new Attributes(
  923.                     fs,
  924.                     file,
  925.                     true, //
  926.                     readAttributes.isDirectory(), //
  927.                     readAttributes.permissions().contains(
  928.                             PosixFilePermission.OWNER_EXECUTE),
  929.                     readAttributes.isSymbolicLink(),
  930.                     readAttributes.isRegularFile(), //
  931.                     readAttributes.creationTime().toMillis(), //
  932.                     readAttributes.lastModifiedTime().toInstant(),
  933.                     readAttributes.size());
  934.             return attributes;
  935.         } catch (IOException e) {
  936.             return new Attributes(file, fs);
  937.         }
  938.     }

  939.     /**
  940.      * NFC normalize a file (on Mac), otherwise do nothing
  941.      *
  942.      * @param file
  943.      *            a {@link java.io.File}.
  944.      * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
  945.      *         file
  946.      * @since 4.1
  947.      */
  948.     public static File normalize(File file) {
  949.         if (SystemReader.getInstance().isMacOS()) {
  950.             // TODO: Would it be faster to check with isNormalized first
  951.             // assuming normalized paths are much more common
  952.             String normalized = Normalizer.normalize(file.getPath(),
  953.                     Normalizer.Form.NFC);
  954.             return new File(normalized);
  955.         }
  956.         return file;
  957.     }

  958.     /**
  959.      * On Mac: get NFC normalized form of given name, otherwise the given name.
  960.      *
  961.      * @param name
  962.      *            a {@link java.lang.String} object.
  963.      * @return on Mac: NFC normalized form of given name
  964.      * @since 4.1
  965.      */
  966.     public static String normalize(String name) {
  967.         if (SystemReader.getInstance().isMacOS()) {
  968.             if (name == null)
  969.                 return null;
  970.             return Normalizer.normalize(name, Normalizer.Form.NFC);
  971.         }
  972.         return name;
  973.     }

  974.     /**
  975.      * Best-effort variation of {@link java.io.File#getCanonicalFile()}
  976.      * returning the input file if the file cannot be canonicalized instead of
  977.      * throwing {@link java.io.IOException}.
  978.      *
  979.      * @param file
  980.      *            to be canonicalized; may be {@code null}
  981.      * @return canonicalized file, or the unchanged input file if
  982.      *         canonicalization failed or if {@code file == null}
  983.      * @throws java.lang.SecurityException
  984.      *             if {@link java.io.File#getCanonicalFile()} throws one
  985.      * @since 4.2
  986.      */
  987.     public static File canonicalize(File file) {
  988.         if (file == null) {
  989.             return null;
  990.         }
  991.         try {
  992.             return file.getCanonicalFile();
  993.         } catch (IOException e) {
  994.             return file;
  995.         }
  996.     }

  997.     /**
  998.      * Convert a path to String, replacing separators as necessary.
  999.      *
  1000.      * @param file
  1001.      *            a {@link java.io.File}.
  1002.      * @return file's path as a String
  1003.      * @since 4.10
  1004.      */
  1005.     public static String pathToString(File file) {
  1006.         final String path = file.getPath();
  1007.         if (SystemReader.getInstance().isWindows()) {
  1008.             return path.replace('\\', '/');
  1009.         }
  1010.         return path;
  1011.     }

  1012.     /**
  1013.      * Touch the given file
  1014.      *
  1015.      * @param f
  1016.      *            the file to touch
  1017.      * @throws IOException
  1018.      * @since 5.1.8
  1019.      */
  1020.     public static void touch(Path f) throws IOException {
  1021.         try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
  1022.                 StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
  1023.             // touch
  1024.         }
  1025.         Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
  1026.     }

  1027.     /**
  1028.      * Compute a delay in a {@code min..max} interval with random jitter.
  1029.      *
  1030.      * @param last
  1031.      *            amount of delay waited before the last attempt. This is used
  1032.      *            to seed the next delay interval. Should be 0 if there was no
  1033.      *            prior delay.
  1034.      * @param min
  1035.      *            shortest amount of allowable delay between attempts.
  1036.      * @param max
  1037.      *            longest amount of allowable delay between attempts.
  1038.      * @return new amount of delay to wait before the next attempt.
  1039.      *
  1040.      * @since 5.6
  1041.      */
  1042.     public static long delay(long last, long min, long max) {
  1043.         long r = Math.max(0, last * 3 - min);
  1044.         if (r > 0) {
  1045.             int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
  1046.             r = RNG.nextInt(c);
  1047.         }
  1048.         return Math.max(Math.min(min + r, max), min);
  1049.     }
  1050. }