You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileUtils.java 31KB

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