View Javadoc
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  
13  package org.eclipse.jgit.util;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  
17  import java.io.File;
18  import java.io.FileNotFoundException;
19  import java.io.IOException;
20  import java.io.InterruptedIOException;
21  import java.nio.channels.FileChannel;
22  import java.nio.file.AtomicMoveNotSupportedException;
23  import java.nio.file.CopyOption;
24  import java.nio.file.DirectoryNotEmptyException;
25  import java.nio.file.Files;
26  import java.nio.file.InvalidPathException;
27  import java.nio.file.LinkOption;
28  import java.nio.file.NoSuchFileException;
29  import java.nio.file.Path;
30  import java.nio.file.StandardCopyOption;
31  import java.nio.file.StandardOpenOption;
32  import java.nio.file.attribute.BasicFileAttributeView;
33  import java.nio.file.attribute.BasicFileAttributes;
34  import java.nio.file.attribute.FileTime;
35  import java.nio.file.attribute.PosixFileAttributeView;
36  import java.nio.file.attribute.PosixFileAttributes;
37  import java.nio.file.attribute.PosixFilePermission;
38  import java.text.MessageFormat;
39  import java.text.Normalizer;
40  import java.text.Normalizer.Form;
41  import java.time.Instant;
42  import java.util.ArrayList;
43  import java.util.List;
44  import java.util.Locale;
45  import java.util.Random;
46  import java.util.regex.Pattern;
47  import java.util.stream.Stream;
48  
49  import org.eclipse.jgit.internal.JGitText;
50  import org.eclipse.jgit.lib.Constants;
51  import org.eclipse.jgit.util.FS.Attributes;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  /**
56   * File Utilities
57   */
58  public class FileUtils {
59  	private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
60  
61  	private static final Random RNG = new Random();
62  
63  	/**
64  	 * Option to delete given {@code File}
65  	 */
66  	public static final int NONE = 0;
67  
68  	/**
69  	 * Option to recursively delete given {@code File}
70  	 */
71  	public static final int RECURSIVE = 1;
72  
73  	/**
74  	 * Option to retry deletion if not successful
75  	 */
76  	public static final int RETRY = 2;
77  
78  	/**
79  	 * Option to skip deletion if file doesn't exist
80  	 */
81  	public static final int SKIP_MISSING = 4;
82  
83  	/**
84  	 * Option not to throw exceptions when a deletion finally doesn't succeed.
85  	 * @since 2.0
86  	 */
87  	public static final int IGNORE_ERRORS = 8;
88  
89  	/**
90  	 * Option to only delete empty directories. This option can be combined with
91  	 * {@link #RECURSIVE}
92  	 *
93  	 * @since 3.0
94  	 */
95  	public static final int EMPTY_DIRECTORIES_ONLY = 16;
96  
97  	/**
98  	 * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}.
99  	 *
100 	 * @param f
101 	 *            {@code File} to be converted to {@code Path}
102 	 * @return the path represented by the file
103 	 * @throws java.io.IOException
104 	 *             in case the path represented by the file is not valid (
105 	 *             {@link java.nio.file.InvalidPathException})
106 	 * @since 4.10
107 	 */
108 	public static Path toPath(File f) throws IOException {
109 		try {
110 			return f.toPath();
111 		} catch (InvalidPathException ex) {
112 			throw new IOException(ex);
113 		}
114 	}
115 
116 	/**
117 	 * Delete file or empty folder
118 	 *
119 	 * @param f
120 	 *            {@code File} to be deleted
121 	 * @throws java.io.IOException
122 	 *             if deletion of {@code f} fails. This may occur if {@code f}
123 	 *             didn't exist when the method was called. This can therefore
124 	 *             cause java.io.IOExceptions during race conditions when
125 	 *             multiple concurrent threads all try to delete the same file.
126 	 */
127 	public static void delete(File f) throws IOException {
128 		delete(f, NONE);
129 	}
130 
131 	/**
132 	 * Delete file or folder
133 	 *
134 	 * @param f
135 	 *            {@code File} to be deleted
136 	 * @param options
137 	 *            deletion options, {@code RECURSIVE} for recursive deletion of
138 	 *            a subtree, {@code RETRY} to retry when deletion failed.
139 	 *            Retrying may help if the underlying file system doesn't allow
140 	 *            deletion of files being read by another thread.
141 	 * @throws java.io.IOException
142 	 *             if deletion of {@code f} fails. This may occur if {@code f}
143 	 *             didn't exist when the method was called. This can therefore
144 	 *             cause java.io.IOExceptions during race conditions when
145 	 *             multiple concurrent threads all try to delete the same file.
146 	 *             This exception is not thrown when IGNORE_ERRORS is set.
147 	 */
148 	public static void delete(File f, int options) throws IOException {
149 		FS fs = FS.DETECTED;
150 		if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
151 			return;
152 
153 		if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
154 			final File[] items = f.listFiles();
155 			if (items != null) {
156 				List<File> files = new ArrayList<>();
157 				List<File> dirs = new ArrayList<>();
158 				for (File c : items)
159 					if (c.isFile())
160 						files.add(c);
161 					else
162 						dirs.add(c);
163 				// Try to delete files first, otherwise options
164 				// EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty
165 				// directories before aborting, depending on order.
166 				for (File file : files)
167 					delete(file, options);
168 				for (File d : dirs)
169 					delete(d, options);
170 			}
171 		}
172 
173 		boolean delete = false;
174 		if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
175 			if (f.isDirectory()) {
176 				delete = true;
177 			} else if ((options & IGNORE_ERRORS) == 0) {
178 				throw new IOException(MessageFormat.format(
179 						JGitText.get().deleteFileFailed, f.getAbsolutePath()));
180 			}
181 		} else {
182 			delete = true;
183 		}
184 
185 		if (delete) {
186 			IOException t = null;
187 			Path p = f.toPath();
188 			boolean tryAgain;
189 			do {
190 				tryAgain = false;
191 				try {
192 					Files.delete(p);
193 					return;
194 				} catch (NoSuchFileException | FileNotFoundException e) {
195 					handleDeleteException(f, e, options,
196 							SKIP_MISSING | IGNORE_ERRORS);
197 					return;
198 				} catch (DirectoryNotEmptyException e) {
199 					handleDeleteException(f, e, options, IGNORE_ERRORS);
200 					return;
201 				} catch (IOException e) {
202 					if (!f.canWrite()) {
203 						tryAgain = f.setWritable(true);
204 					}
205 					if (!tryAgain) {
206 						t = e;
207 					}
208 				}
209 			} while (tryAgain);
210 
211 			if ((options & RETRY) != 0) {
212 				for (int i = 1; i < 10; i++) {
213 					try {
214 						Thread.sleep(100);
215 					} catch (InterruptedException ex) {
216 						// ignore
217 					}
218 					try {
219 						Files.deleteIfExists(p);
220 						return;
221 					} catch (IOException e) {
222 						t = e;
223 					}
224 				}
225 			}
226 			handleDeleteException(f, t, options, IGNORE_ERRORS);
227 		}
228 	}
229 
230 	private static void handleDeleteException(File f, IOException e,
231 			int allOptions, int checkOptions) throws IOException {
232 		if (e != null && (allOptions & checkOptions) == 0) {
233 			throw new IOException(MessageFormat.format(
234 					JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
235 		}
236 	}
237 
238 	/**
239 	 * Rename a file or folder. If the rename fails and if we are running on a
240 	 * filesystem where it makes sense to repeat a failing rename then repeat
241 	 * the rename operation up to 9 times with 100ms sleep time between two
242 	 * calls. Furthermore if the destination exists and is directory hierarchy
243 	 * with only directories in it, the whole directory hierarchy will be
244 	 * deleted. If the target represents a non-empty directory structure, empty
245 	 * subdirectories within that structure may or may not be deleted even if
246 	 * the method fails. Furthermore if the destination exists and is a file
247 	 * then the file will be deleted and then the rename is retried.
248 	 * <p>
249 	 * This operation is <em>not</em> atomic.
250 	 *
251 	 * @see FS#retryFailedLockFileCommit()
252 	 * @param src
253 	 *            the old {@code File}
254 	 * @param dst
255 	 *            the new {@code File}
256 	 * @throws java.io.IOException
257 	 *             if the rename has failed
258 	 * @since 3.0
259 	 */
260 	public static void rename(File src, File dst)
261 			throws IOException {
262 		rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
263 	}
264 
265 	/**
266 	 * Rename a file or folder using the passed
267 	 * {@link java.nio.file.CopyOption}s. If the rename fails and if we are
268 	 * running on a filesystem where it makes sense to repeat a failing rename
269 	 * then repeat the rename operation up to 9 times with 100ms sleep time
270 	 * between two calls. Furthermore if the destination exists and is a
271 	 * directory hierarchy with only directories in it, the whole directory
272 	 * hierarchy will be deleted. If the target represents a non-empty directory
273 	 * structure, empty subdirectories within that structure may or may not be
274 	 * deleted even if the method fails. Furthermore if the destination exists
275 	 * and is a file then the file will be replaced if
276 	 * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set.
277 	 * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the
278 	 * rename will be done atomically or fail with an
279 	 * {@link java.nio.file.AtomicMoveNotSupportedException}
280 	 *
281 	 * @param src
282 	 *            the old file
283 	 * @param dst
284 	 *            the new file
285 	 * @param options
286 	 *            options to pass to
287 	 *            {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)}
288 	 * @throws java.nio.file.AtomicMoveNotSupportedException
289 	 *             if file cannot be moved as an atomic file system operation
290 	 * @throws java.io.IOException
291 	 * @since 4.1
292 	 */
293 	public static void rename(final File src, final File dst,
294 			CopyOption... options)
295 					throws AtomicMoveNotSupportedException, IOException {
296 		int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
297 		while (--attempts >= 0) {
298 			try {
299 				Files.move(toPath(src), toPath(dst), options);
300 				return;
301 			} catch (AtomicMoveNotSupportedException e) {
302 				throw e;
303 			} catch (IOException e) {
304 				try {
305 					if (!dst.delete()) {
306 						delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
307 					}
308 					// On *nix there is no try, you do or do not
309 					Files.move(toPath(src), toPath(dst), options);
310 					return;
311 				} catch (IOException e2) {
312 					// ignore and continue retry
313 				}
314 			}
315 			try {
316 				Thread.sleep(100);
317 			} catch (InterruptedException e) {
318 				throw new IOException(
319 						MessageFormat.format(JGitText.get().renameFileFailed,
320 								src.getAbsolutePath(), dst.getAbsolutePath()),
321 						e);
322 			}
323 		}
324 		throw new IOException(
325 				MessageFormat.format(JGitText.get().renameFileFailed,
326 						src.getAbsolutePath(), dst.getAbsolutePath()));
327 	}
328 
329 	/**
330 	 * Creates the directory named by this abstract pathname.
331 	 *
332 	 * @param d
333 	 *            directory to be created
334 	 * @throws java.io.IOException
335 	 *             if creation of {@code d} fails. This may occur if {@code d}
336 	 *             did exist when the method was called. This can therefore
337 	 *             cause java.io.IOExceptions during race conditions when
338 	 *             multiple concurrent threads all try to create the same
339 	 *             directory.
340 	 */
341 	public static void mkdir(File d)
342 			throws IOException {
343 		mkdir(d, false);
344 	}
345 
346 	/**
347 	 * Creates the directory named by this abstract pathname.
348 	 *
349 	 * @param d
350 	 *            directory to be created
351 	 * @param skipExisting
352 	 *            if {@code true} skip creation of the given directory if it
353 	 *            already exists in the file system
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 mkdir(File d, boolean skipExisting)
362 			throws IOException {
363 		if (!d.mkdir()) {
364 			if (skipExisting && d.isDirectory())
365 				return;
366 			throw new IOException(MessageFormat.format(
367 					JGitText.get().mkDirFailed, d.getAbsolutePath()));
368 		}
369 	}
370 
371 	/**
372 	 * Creates the directory named by this abstract pathname, including any
373 	 * necessary but nonexistent parent directories. Note that if this operation
374 	 * fails it may have succeeded in creating some of the necessary parent
375 	 * directories.
376 	 *
377 	 * @param d
378 	 *            directory to be created
379 	 * @throws java.io.IOException
380 	 *             if creation of {@code d} fails. This may occur if {@code d}
381 	 *             did exist when the method was called. This can therefore
382 	 *             cause java.io.IOExceptions during race conditions when
383 	 *             multiple concurrent threads all try to create the same
384 	 *             directory.
385 	 */
386 	public static void mkdirs(File d) throws IOException {
387 		mkdirs(d, false);
388 	}
389 
390 	/**
391 	 * Creates the directory named by this abstract pathname, including any
392 	 * necessary but nonexistent parent directories. Note that if this operation
393 	 * fails it may have succeeded in creating some of the necessary parent
394 	 * directories.
395 	 *
396 	 * @param d
397 	 *            directory to be created
398 	 * @param skipExisting
399 	 *            if {@code true} skip creation of the given directory if it
400 	 *            already exists in the file system
401 	 * @throws java.io.IOException
402 	 *             if creation of {@code d} fails. This may occur if {@code d}
403 	 *             did exist when the method was called. This can therefore
404 	 *             cause java.io.IOExceptions during race conditions when
405 	 *             multiple concurrent threads all try to create the same
406 	 *             directory.
407 	 */
408 	public static void mkdirs(File d, boolean skipExisting)
409 			throws IOException {
410 		if (!d.mkdirs()) {
411 			if (skipExisting && d.isDirectory())
412 				return;
413 			throw new IOException(MessageFormat.format(
414 					JGitText.get().mkDirsFailed, d.getAbsolutePath()));
415 		}
416 	}
417 
418 	/**
419 	 * Atomically creates a new, empty file named by this abstract pathname if
420 	 * and only if a file with this name does not yet exist. The check for the
421 	 * existence of the file and the creation of the file if it does not exist
422 	 * are a single operation that is atomic with respect to all other
423 	 * filesystem activities that might affect the file.
424 	 * <p>
425 	 * Note: this method should not be used for file-locking, as the resulting
426 	 * protocol cannot be made to work reliably. The
427 	 * {@link java.nio.channels.FileLock} facility should be used instead.
428 	 *
429 	 * @param f
430 	 *            the file to be created
431 	 * @throws java.io.IOException
432 	 *             if the named file already exists or if an I/O error occurred
433 	 */
434 	public static void createNewFile(File f) throws IOException {
435 		if (!f.createNewFile())
436 			throw new IOException(MessageFormat.format(
437 					JGitText.get().createNewFileFailed, f));
438 	}
439 
440 	/**
441 	 * Create a symbolic link
442 	 *
443 	 * @param path
444 	 *            the path of the symbolic link to create
445 	 * @param target
446 	 *            the target of the symbolic link
447 	 * @return the path to the symbolic link
448 	 * @throws java.io.IOException
449 	 * @since 4.2
450 	 */
451 	public static Path createSymLink(File path, String target)
452 			throws IOException {
453 		Path nioPath = toPath(path);
454 		if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
455 			BasicFileAttributes attrs = Files.readAttributes(nioPath,
456 					BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
457 			if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
458 				delete(path);
459 			} else {
460 				delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
461 			}
462 		}
463 		if (SystemReader.getInstance().isWindows()) {
464 			target = target.replace('/', '\\');
465 		}
466 		Path nioTarget = toPath(new File(target));
467 		return Files.createSymbolicLink(nioPath, nioTarget);
468 	}
469 
470 	/**
471 	 * Read target path of the symlink.
472 	 *
473 	 * @param path
474 	 *            a {@link java.io.File} object.
475 	 * @return target path of the symlink, or null if it is not a symbolic link
476 	 * @throws java.io.IOException
477 	 * @since 3.0
478 	 */
479 	public static String readSymLink(File path) throws IOException {
480 		Path nioPath = toPath(path);
481 		Path target = Files.readSymbolicLink(nioPath);
482 		String targetString = target.toString();
483 		if (SystemReader.getInstance().isWindows()) {
484 			targetString = targetString.replace('\\', '/');
485 		} else if (SystemReader.getInstance().isMacOS()) {
486 			targetString = Normalizer.normalize(targetString, Form.NFC);
487 		}
488 		return targetString;
489 	}
490 
491 	/**
492 	 * Create a temporary directory.
493 	 *
494 	 * @param prefix
495 	 *            prefix string
496 	 * @param suffix
497 	 *            suffix string
498 	 * @param dir
499 	 *            The parent dir, can be null to use system default temp dir.
500 	 * @return the temp dir created.
501 	 * @throws java.io.IOException
502 	 * @since 3.4
503 	 */
504 	public static File createTempDir(String prefix, String suffix, File dir)
505 			throws IOException {
506 		final int RETRIES = 1; // When something bad happens, retry once.
507 		for (int i = 0; i < RETRIES; i++) {
508 			File tmp = File.createTempFile(prefix, suffix, dir);
509 			if (!tmp.delete())
510 				continue;
511 			if (!tmp.mkdir())
512 				continue;
513 			return tmp;
514 		}
515 		throw new IOException(JGitText.get().cannotCreateTempDir);
516 	}
517 
518 	/**
519 	 * Expresses <code>other</code> as a relative file path from
520 	 * <code>base</code>. File-separator and case sensitivity are based on the
521 	 * current file system.
522 	 *
523 	 * See also
524 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
525 	 *
526 	 * @param base
527 	 *            Base path
528 	 * @param other
529 	 *            Destination path
530 	 * @return Relative path from <code>base</code> to <code>other</code>
531 	 * @since 4.8
532 	 */
533 	public static String relativizeNativePath(String base, String other) {
534 		return FS.DETECTED.relativize(base, other);
535 	}
536 
537 	/**
538 	 * Expresses <code>other</code> as a relative file path from
539 	 * <code>base</code>. File-separator and case sensitivity are based on Git's
540 	 * internal representation of files (which matches Unix).
541 	 *
542 	 * See also
543 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
544 	 *
545 	 * @param base
546 	 *            Base path
547 	 * @param other
548 	 *            Destination path
549 	 * @return Relative path from <code>base</code> to <code>other</code>
550 	 * @since 4.8
551 	 */
552 	public static String relativizeGitPath(String base, String other) {
553 		return relativizePath(base, other, "/", false); //$NON-NLS-1$
554 	}
555 
556 
557 	/**
558 	 * Expresses <code>other</code> as a relative file path from <code>base</code>
559 	 * <p>
560 	 * For example, if called with the two following paths :
561 	 *
562 	 * <pre>
563 	 * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
564 	 * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
565 	 * </pre>
566 	 *
567 	 * This will return "..\\another_project\\pom.xml".
568 	 *
569 	 * <p>
570 	 * <b>Note</b> that this will return the empty String if <code>base</code>
571 	 * and <code>other</code> are equal.
572 	 * </p>
573 	 *
574 	 * @param base
575 	 *            The path against which <code>other</code> should be
576 	 *            relativized. This will be assumed to denote the path to a
577 	 *            folder and not a file.
578 	 * @param other
579 	 *            The path that will be made relative to <code>base</code>.
580 	 * @param dirSeparator
581 	 *            A string that separates components of the path. In practice, this is "/" or "\\".
582 	 * @param caseSensitive
583 	 *            Whether to consider differently-cased directory names as distinct
584 	 * @return A relative path that, when resolved against <code>base</code>,
585 	 *         will yield the original <code>other</code>.
586 	 * @since 4.8
587 	 */
588 	public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
589 		if (base.equals(other))
590 			return ""; //$NON-NLS-1$
591 
592 		final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
593 		final String[] otherSegments = other.split(Pattern
594 				.quote(dirSeparator));
595 
596 		int commonPrefix = 0;
597 		while (commonPrefix < baseSegments.length
598 				&& commonPrefix < otherSegments.length) {
599 			if (caseSensitive
600 					&& baseSegments[commonPrefix]
601 					.equals(otherSegments[commonPrefix]))
602 				commonPrefix++;
603 			else if (!caseSensitive
604 					&& baseSegments[commonPrefix]
605 							.equalsIgnoreCase(otherSegments[commonPrefix]))
606 				commonPrefix++;
607 			else
608 				break;
609 		}
610 
611 		final StringBuilder builder = new StringBuilder();
612 		for (int i = commonPrefix; i < baseSegments.length; i++)
613 			builder.append("..").append(dirSeparator); //$NON-NLS-1$
614 		for (int i = commonPrefix; i < otherSegments.length; i++) {
615 			builder.append(otherSegments[i]);
616 			if (i < otherSegments.length - 1)
617 				builder.append(dirSeparator);
618 		}
619 		return builder.toString();
620 	}
621 
622 	/**
623 	 * Determine if an IOException is a Stale NFS File Handle
624 	 *
625 	 * @param ioe
626 	 *            an {@link java.io.IOException} object.
627 	 * @return a boolean true if the IOException is a Stale NFS FIle Handle
628 	 * @since 4.1
629 	 */
630 	public static boolean isStaleFileHandle(IOException ioe) {
631 		String msg = ioe.getMessage();
632 		return msg != null
633 				&& msg.toLowerCase(Locale.ROOT)
634 						.matches("stale .*file .*handle"); //$NON-NLS-1$
635 	}
636 
637 	/**
638 	 * Determine if a throwable or a cause in its causal chain is a Stale NFS
639 	 * File Handle
640 	 *
641 	 * @param throwable
642 	 *            a {@link java.lang.Throwable} object.
643 	 * @return a boolean true if the throwable or a cause in its causal chain is
644 	 *         a Stale NFS File Handle
645 	 * @since 4.7
646 	 */
647 	public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
648 		while (throwable != null) {
649 			if (throwable instanceof IOException
650 					&& isStaleFileHandle((IOException) throwable)) {
651 				return true;
652 			}
653 			throwable = throwable.getCause();
654 		}
655 		return false;
656 	}
657 
658 	/**
659 	 * Like a {@link java.util.function.Function} but throwing an
660 	 * {@link Exception}.
661 	 *
662 	 * @param <A>
663 	 *            input type
664 	 * @param <B>
665 	 *            output type
666 	 * @since 6.2
667 	 */
668 	@FunctionalInterface
669 	public interface IOFunction<A, B> {
670 
671 		/**
672 		 * Performs the function.
673 		 *
674 		 * @param t
675 		 *            input to operate on
676 		 * @return the output
677 		 * @throws Exception
678 		 *             if a problem occurs
679 		 */
680 		B apply(A t) throws Exception;
681 	}
682 
683 	private static void backOff(long delay, IOException cause)
684 			throws IOException {
685 		try {
686 			Thread.sleep(delay);
687 		} catch (InterruptedException e) {
688 			IOException interruption = new InterruptedIOException();
689 			interruption.initCause(e);
690 			interruption.addSuppressed(cause);
691 			Thread.currentThread().interrupt(); // Re-set flag
692 			throw interruption;
693 		}
694 	}
695 
696 	/**
697 	 * Invokes the given {@link IOFunction}, performing a limited number of
698 	 * re-tries if exceptions occur that indicate either a stale NFS file handle
699 	 * or that indicate that the file may be written concurrently.
700 	 *
701 	 * @param <T>
702 	 *            result type
703 	 * @param file
704 	 *            to read
705 	 * @param reader
706 	 *            for reading the file and creating an instance of {@code T}
707 	 * @return the result of the {@code reader}, or {@code null} if the file
708 	 *         does not exist
709 	 * @throws Exception
710 	 *             if a problem occurs
711 	 * @since 6.2
712 	 */
713 	public static <T> T readWithRetries(File file,
714 			IOFunction<File, ? extends T> reader)
715 			throws Exception {
716 		int maxStaleRetries = 5;
717 		int retries = 0;
718 		long backoff = 50;
719 		while (true) {
720 			try {
721 				try {
722 					return reader.apply(file);
723 				} catch (IOException e) {
724 					if (FileUtils.isStaleFileHandleInCausalChain(e)
725 							&& retries < maxStaleRetries) {
726 						if (LOG.isDebugEnabled()) {
727 							LOG.debug(MessageFormat.format(
728 									JGitText.get().packedRefsHandleIsStale,
729 									Integer.valueOf(retries)), e);
730 						}
731 						retries++;
732 						continue;
733 					}
734 					throw e;
735 				}
736 			} catch (FileNotFoundException noFile) {
737 				if (!file.isFile()) {
738 					return null;
739 				}
740 				// Probably Windows and some other thread is writing the file
741 				// concurrently.
742 				if (backoff > 1000) {
743 					throw noFile;
744 				}
745 				backOff(backoff, noFile);
746 				backoff *= 2; // 50, 100, 200, 400, 800 ms
747 			}
748 		}
749 	}
750 
751 	/**
752 	 * @param file
753 	 * @return {@code true} if the passed file is a symbolic link
754 	 */
755 	static boolean isSymlink(File file) {
756 		return Files.isSymbolicLink(file.toPath());
757 	}
758 
759 	/**
760 	 * @param file
761 	 * @return lastModified attribute for given file, not following symbolic
762 	 *         links
763 	 * @throws IOException
764 	 * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
765 	 *             FileTime
766 	 */
767 	@Deprecated
768 	static long lastModified(File file) throws IOException {
769 		return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
770 				.toMillis();
771 	}
772 
773 	/**
774 	 * @param path
775 	 * @return lastModified attribute for given file, not following symbolic
776 	 *         links
777 	 */
778 	static Instant lastModifiedInstant(Path path) {
779 		try {
780 			return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
781 					.toInstant();
782 		} catch (NoSuchFileException e) {
783 			LOG.debug(
784 					"Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
785 					path);
786 			return Instant.EPOCH;
787 		} catch (IOException e) {
788 			LOG.error(MessageFormat
789 					.format(JGitText.get().readLastModifiedFailed, path), e);
790 			return Instant.ofEpochMilli(path.toFile().lastModified());
791 		}
792 	}
793 
794 	/**
795 	 * Return all the attributes of a file, without following symbolic links.
796 	 *
797 	 * @param file
798 	 * @return {@link BasicFileAttributes} of the file
799 	 * @throws IOException in case of any I/O errors accessing the file
800 	 *
801 	 * @since 4.5.6
802 	 */
803 	static BasicFileAttributes fileAttributes(File file) throws IOException {
804 		return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
805 	}
806 
807 	/**
808 	 * Set the last modified time of a file system object.
809 	 *
810 	 * @param file
811 	 * @param time
812 	 * @throws IOException
813 	 */
814 	@Deprecated
815 	static void setLastModified(File file, long time) throws IOException {
816 		Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
817 	}
818 
819 	/**
820 	 * Set the last modified time of a file system object.
821 	 *
822 	 * @param path
823 	 * @param time
824 	 * @throws IOException
825 	 */
826 	static void setLastModified(Path path, Instant time)
827 			throws IOException {
828 		Files.setLastModifiedTime(path, FileTime.from(time));
829 	}
830 
831 	/**
832 	 * @param file
833 	 * @return {@code true} if the given file exists, not following symbolic
834 	 *         links
835 	 */
836 	static boolean exists(File file) {
837 		return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
838 	}
839 
840 	/**
841 	 * @param file
842 	 * @return {@code true} if the given file is hidden
843 	 * @throws IOException
844 	 */
845 	static boolean isHidden(File file) throws IOException {
846 		return Files.isHidden(toPath(file));
847 	}
848 
849 	/**
850 	 * Set a file hidden (on Windows)
851 	 *
852 	 * @param file
853 	 *            a {@link java.io.File} object.
854 	 * @param hidden
855 	 *            a boolean.
856 	 * @throws java.io.IOException
857 	 * @since 4.1
858 	 */
859 	public static void setHidden(File file, boolean hidden) throws IOException {
860 		Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$
861 				LinkOption.NOFOLLOW_LINKS);
862 	}
863 
864 	/**
865 	 * Get file length
866 	 *
867 	 * @param file
868 	 *            a {@link java.io.File}.
869 	 * @return length of the given file
870 	 * @throws java.io.IOException
871 	 * @since 4.1
872 	 */
873 	public static long getLength(File file) throws IOException {
874 		Path nioPath = toPath(file);
875 		if (Files.isSymbolicLink(nioPath))
876 			return Files.readSymbolicLink(nioPath).toString()
877 					.getBytes(UTF_8).length;
878 		return Files.size(nioPath);
879 	}
880 
881 	/**
882 	 * @param file
883 	 * @return {@code true} if the given file is a directory, not following
884 	 *         symbolic links
885 	 */
886 	static boolean isDirectory(File file) {
887 		return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
888 	}
889 
890 	/**
891 	 * @param file
892 	 * @return {@code true} if the given file is a file, not following symbolic
893 	 *         links
894 	 */
895 	static boolean isFile(File file) {
896 		return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
897 	}
898 
899 	/**
900 	 * Whether the path is a directory with files in it.
901 	 *
902 	 * @param dir
903 	 *            directory path
904 	 * @return {@code true} if the given directory path contains files
905 	 * @throws IOException
906 	 *             on any I/O errors accessing the path
907 	 *
908 	 * @since 5.11
909 	 */
910 	public static boolean hasFiles(Path dir) throws IOException {
911 		try (Stream<Path> stream = Files.list(dir)) {
912 			return stream.findAny().isPresent();
913 		}
914 	}
915 
916 	/**
917 	 * Whether the given file can be executed.
918 	 *
919 	 * @param file
920 	 *            a {@link java.io.File} object.
921 	 * @return {@code true} if the given file can be executed.
922 	 * @since 4.1
923 	 */
924 	public static boolean canExecute(File file) {
925 		if (!isFile(file)) {
926 			return false;
927 		}
928 		return Files.isExecutable(file.toPath());
929 	}
930 
931 	/**
932 	 * @param fs
933 	 * @param file
934 	 * @return non null attributes object
935 	 */
936 	static Attributes getFileAttributesBasic(FS fs, File file) {
937 		try {
938 			Path nioPath = toPath(file);
939 			BasicFileAttributes readAttributes = nioPath
940 					.getFileSystem()
941 					.provider()
942 					.getFileAttributeView(nioPath,
943 							BasicFileAttributeView.class,
944 							LinkOption.NOFOLLOW_LINKS).readAttributes();
945 			Attributes attributes = new Attributes(fs, file,
946 					true,
947 					readAttributes.isDirectory(),
948 					fs.supportsExecute() ? file.canExecute() : false,
949 					readAttributes.isSymbolicLink(),
950 					readAttributes.isRegularFile(), //
951 					readAttributes.creationTime().toMillis(), //
952 					readAttributes.lastModifiedTime().toInstant(),
953 					readAttributes.isSymbolicLink() ? Constants
954 							.encode(readSymLink(file)).length
955 							: readAttributes.size());
956 			return attributes;
957 		} catch (IOException e) {
958 			return new Attributes(file, fs);
959 		}
960 	}
961 
962 	/**
963 	 * Get file system attributes for the given file.
964 	 *
965 	 * @param fs
966 	 *            a {@link org.eclipse.jgit.util.FS} object.
967 	 * @param file
968 	 *            a {@link java.io.File}.
969 	 * @return file system attributes for the given file.
970 	 * @since 4.1
971 	 */
972 	public static Attributes getFileAttributesPosix(FS fs, File file) {
973 		try {
974 			Path nioPath = toPath(file);
975 			PosixFileAttributes readAttributes = nioPath
976 					.getFileSystem()
977 					.provider()
978 					.getFileAttributeView(nioPath,
979 							PosixFileAttributeView.class,
980 							LinkOption.NOFOLLOW_LINKS).readAttributes();
981 			Attributes attributes = new Attributes(
982 					fs,
983 					file,
984 					true, //
985 					readAttributes.isDirectory(), //
986 					readAttributes.permissions().contains(
987 							PosixFilePermission.OWNER_EXECUTE),
988 					readAttributes.isSymbolicLink(),
989 					readAttributes.isRegularFile(), //
990 					readAttributes.creationTime().toMillis(), //
991 					readAttributes.lastModifiedTime().toInstant(),
992 					readAttributes.size());
993 			return attributes;
994 		} catch (IOException e) {
995 			return new Attributes(file, fs);
996 		}
997 	}
998 
999 	/**
1000 	 * NFC normalize a file (on Mac), otherwise do nothing
1001 	 *
1002 	 * @param file
1003 	 *            a {@link java.io.File}.
1004 	 * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed
1005 	 *         file
1006 	 * @since 4.1
1007 	 */
1008 	public static File normalize(File file) {
1009 		if (SystemReader.getInstance().isMacOS()) {
1010 			// TODO: Would it be faster to check with isNormalized first
1011 			// assuming normalized paths are much more common
1012 			String normalized = Normalizer.normalize(file.getPath(),
1013 					Normalizer.Form.NFC);
1014 			return new File(normalized);
1015 		}
1016 		return file;
1017 	}
1018 
1019 	/**
1020 	 * On Mac: get NFC normalized form of given name, otherwise the given name.
1021 	 *
1022 	 * @param name
1023 	 *            a {@link java.lang.String} object.
1024 	 * @return on Mac: NFC normalized form of given name
1025 	 * @since 4.1
1026 	 */
1027 	public static String normalize(String name) {
1028 		if (SystemReader.getInstance().isMacOS()) {
1029 			if (name == null)
1030 				return null;
1031 			return Normalizer.normalize(name, Normalizer.Form.NFC);
1032 		}
1033 		return name;
1034 	}
1035 
1036 	/**
1037 	 * Best-effort variation of {@link java.io.File#getCanonicalFile()}
1038 	 * returning the input file if the file cannot be canonicalized instead of
1039 	 * throwing {@link java.io.IOException}.
1040 	 *
1041 	 * @param file
1042 	 *            to be canonicalized; may be {@code null}
1043 	 * @return canonicalized file, or the unchanged input file if
1044 	 *         canonicalization failed or if {@code file == null}
1045 	 * @throws java.lang.SecurityException
1046 	 *             if {@link java.io.File#getCanonicalFile()} throws one
1047 	 * @since 4.2
1048 	 */
1049 	public static File canonicalize(File file) {
1050 		if (file == null) {
1051 			return null;
1052 		}
1053 		try {
1054 			return file.getCanonicalFile();
1055 		} catch (IOException e) {
1056 			return file;
1057 		}
1058 	}
1059 
1060 	/**
1061 	 * Convert a path to String, replacing separators as necessary.
1062 	 *
1063 	 * @param file
1064 	 *            a {@link java.io.File}.
1065 	 * @return file's path as a String
1066 	 * @since 4.10
1067 	 */
1068 	public static String pathToString(File file) {
1069 		final String path = file.getPath();
1070 		if (SystemReader.getInstance().isWindows()) {
1071 			return path.replace('\\', '/');
1072 		}
1073 		return path;
1074 	}
1075 
1076 	/**
1077 	 * Touch the given file
1078 	 *
1079 	 * @param f
1080 	 *            the file to touch
1081 	 * @throws IOException
1082 	 * @since 5.1.8
1083 	 */
1084 	public static void touch(Path f) throws IOException {
1085 		try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
1086 				StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
1087 			// touch
1088 		}
1089 		Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
1090 	}
1091 
1092 	/**
1093 	 * Compute a delay in a {@code min..max} interval with random jitter.
1094 	 *
1095 	 * @param last
1096 	 *            amount of delay waited before the last attempt. This is used
1097 	 *            to seed the next delay interval. Should be 0 if there was no
1098 	 *            prior delay.
1099 	 * @param min
1100 	 *            shortest amount of allowable delay between attempts.
1101 	 * @param max
1102 	 *            longest amount of allowable delay between attempts.
1103 	 * @return new amount of delay to wait before the next attempt.
1104 	 *
1105 	 * @since 5.6
1106 	 */
1107 	public static long delay(long last, long min, long max) {
1108 		long r = Math.max(0, last * 3 - min);
1109 		if (r > 0) {
1110 			int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
1111 			r = RNG.nextInt(c);
1112 		}
1113 		return Math.max(Math.min(min + r, max), min);
1114 	}
1115 }