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 30KB

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