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.

FS.java 66KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.util;
  44. import static java.nio.charset.StandardCharsets.UTF_8;
  45. import static java.time.Instant.EPOCH;
  46. import java.io.BufferedReader;
  47. import java.io.ByteArrayInputStream;
  48. import java.io.Closeable;
  49. import java.io.File;
  50. import java.io.IOException;
  51. import java.io.InputStream;
  52. import java.io.InputStreamReader;
  53. import java.io.OutputStream;
  54. import java.io.OutputStreamWriter;
  55. import java.io.PrintStream;
  56. import java.io.Writer;
  57. import java.nio.charset.Charset;
  58. import java.nio.file.AccessDeniedException;
  59. import java.nio.file.FileStore;
  60. import java.nio.file.Files;
  61. import java.nio.file.Path;
  62. import java.nio.file.attribute.BasicFileAttributes;
  63. import java.nio.file.attribute.FileTime;
  64. import java.security.AccessController;
  65. import java.security.PrivilegedAction;
  66. import java.text.MessageFormat;
  67. import java.time.Duration;
  68. import java.time.Instant;
  69. import java.util.ArrayList;
  70. import java.util.Arrays;
  71. import java.util.HashMap;
  72. import java.util.Map;
  73. import java.util.Objects;
  74. import java.util.Optional;
  75. import java.util.UUID;
  76. import java.util.concurrent.CancellationException;
  77. import java.util.concurrent.CompletableFuture;
  78. import java.util.concurrent.ConcurrentHashMap;
  79. import java.util.concurrent.ExecutionException;
  80. import java.util.concurrent.ExecutorService;
  81. import java.util.concurrent.Executors;
  82. import java.util.concurrent.TimeUnit;
  83. import java.util.concurrent.TimeoutException;
  84. import java.util.concurrent.atomic.AtomicBoolean;
  85. import java.util.concurrent.atomic.AtomicReference;
  86. import java.util.concurrent.locks.Lock;
  87. import java.util.concurrent.locks.ReentrantLock;
  88. import org.eclipse.jgit.annotations.NonNull;
  89. import org.eclipse.jgit.annotations.Nullable;
  90. import org.eclipse.jgit.api.errors.JGitInternalException;
  91. import org.eclipse.jgit.errors.CommandFailedException;
  92. import org.eclipse.jgit.errors.ConfigInvalidException;
  93. import org.eclipse.jgit.errors.LockFailedException;
  94. import org.eclipse.jgit.internal.JGitText;
  95. import org.eclipse.jgit.internal.storage.file.FileSnapshot;
  96. import org.eclipse.jgit.lib.ConfigConstants;
  97. import org.eclipse.jgit.lib.Constants;
  98. import org.eclipse.jgit.lib.Repository;
  99. import org.eclipse.jgit.lib.StoredConfig;
  100. import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
  101. import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
  102. import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
  103. import org.eclipse.jgit.util.ProcessResult.Status;
  104. import org.slf4j.Logger;
  105. import org.slf4j.LoggerFactory;
  106. /**
  107. * Abstraction to support various file system operations not in Java.
  108. */
  109. public abstract class FS {
  110. private static final Logger LOG = LoggerFactory.getLogger(FS.class);
  111. /**
  112. * An empty array of entries, suitable as a return value for
  113. * {@link #list(File, FileModeStrategy)}.
  114. *
  115. * @since 5.0
  116. */
  117. protected static final Entry[] NO_ENTRIES = {};
  118. /**
  119. * This class creates FS instances. It will be overridden by a Java7 variant
  120. * if such can be detected in {@link #detect(Boolean)}.
  121. *
  122. * @since 3.0
  123. */
  124. public static class FSFactory {
  125. /**
  126. * Constructor
  127. */
  128. protected FSFactory() {
  129. // empty
  130. }
  131. /**
  132. * Detect the file system
  133. *
  134. * @param cygwinUsed
  135. * @return FS instance
  136. */
  137. public FS detect(Boolean cygwinUsed) {
  138. if (SystemReader.getInstance().isWindows()) {
  139. if (cygwinUsed == null)
  140. cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  141. if (cygwinUsed.booleanValue())
  142. return new FS_Win32_Cygwin();
  143. else
  144. return new FS_Win32();
  145. } else {
  146. return new FS_POSIX();
  147. }
  148. }
  149. }
  150. /**
  151. * Result of an executed process. The caller is responsible to close the
  152. * contained {@link TemporaryBuffer}s
  153. *
  154. * @since 4.2
  155. */
  156. public static class ExecutionResult {
  157. private TemporaryBuffer stdout;
  158. private TemporaryBuffer stderr;
  159. private int rc;
  160. /**
  161. * @param stdout
  162. * @param stderr
  163. * @param rc
  164. */
  165. public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
  166. int rc) {
  167. this.stdout = stdout;
  168. this.stderr = stderr;
  169. this.rc = rc;
  170. }
  171. /**
  172. * @return buffered standard output stream
  173. */
  174. public TemporaryBuffer getStdout() {
  175. return stdout;
  176. }
  177. /**
  178. * @return buffered standard error stream
  179. */
  180. public TemporaryBuffer getStderr() {
  181. return stderr;
  182. }
  183. /**
  184. * @return the return code of the process
  185. */
  186. public int getRc() {
  187. return rc;
  188. }
  189. }
  190. /**
  191. * Attributes of FileStores on this system
  192. *
  193. * @since 5.1.9
  194. */
  195. public final static class FileStoreAttributes {
  196. private static final Duration UNDEFINED_DURATION = Duration
  197. .ofNanos(Long.MAX_VALUE);
  198. /**
  199. * Fallback filesystem timestamp resolution. The worst case timestamp
  200. * resolution on FAT filesystems is 2 seconds.
  201. */
  202. public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
  203. .ofMillis(2000);
  204. /**
  205. * Fallback FileStore attributes used when we can't measure the
  206. * filesystem timestamp resolution. The last modified time granularity
  207. * of FAT filesystems is 2 seconds.
  208. */
  209. public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
  210. FALLBACK_TIMESTAMP_RESOLUTION);
  211. private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
  212. private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
  213. 100, 0.2f);
  214. private static AtomicBoolean background = new AtomicBoolean();
  215. private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
  216. private static void setBackground(boolean async) {
  217. background.set(async);
  218. }
  219. private static final String javaVersionPrefix = System
  220. .getProperty("java.vendor") + '|' //$NON-NLS-1$
  221. + System.getProperty("java.version") + '|'; //$NON-NLS-1$
  222. private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
  223. .ofMillis(10);
  224. /**
  225. * Configures size and purge factor of the path-based cache for file
  226. * system attributes. Caching of file system attributes avoids recurring
  227. * lookup of @{code FileStore} of files which may be expensive on some
  228. * platforms.
  229. *
  230. * @param maxSize
  231. * maximum size of the cache, default is 100
  232. * @param purgeFactor
  233. * when the size of the map reaches maxSize the oldest
  234. * entries will be purged to free up some space for new
  235. * entries, {@code purgeFactor} is the fraction of
  236. * {@code maxSize} to purge when this happens
  237. * @since 5.1.9
  238. */
  239. public static void configureAttributesPathCache(int maxSize,
  240. float purgeFactor) {
  241. FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
  242. }
  243. /**
  244. * Get the FileStoreAttributes for the given FileStore
  245. *
  246. * @param path
  247. * file residing in the FileStore to get attributes for
  248. * @return FileStoreAttributes for the given path.
  249. */
  250. public static FileStoreAttributes get(Path path) {
  251. path = path.toAbsolutePath();
  252. Path dir = Files.isDirectory(path) ? path : path.getParent();
  253. FileStoreAttributes cached = attrCacheByPath.get(dir);
  254. if (cached != null) {
  255. return cached;
  256. }
  257. FileStoreAttributes attrs = getFileStoreAttributes(dir);
  258. attrCacheByPath.put(dir, attrs);
  259. return attrs;
  260. }
  261. private static FileStoreAttributes getFileStoreAttributes(Path dir) {
  262. FileStore s;
  263. try {
  264. if (Files.exists(dir)) {
  265. s = Files.getFileStore(dir);
  266. FileStoreAttributes c = attributeCache.get(s);
  267. if (c != null) {
  268. return c;
  269. }
  270. if (!Files.isWritable(dir)) {
  271. // cannot measure resolution in a read-only directory
  272. LOG.debug(
  273. "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
  274. Thread.currentThread(), dir);
  275. return FALLBACK_FILESTORE_ATTRIBUTES;
  276. }
  277. } else {
  278. // cannot determine FileStore of an unborn directory
  279. LOG.debug(
  280. "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
  281. Thread.currentThread(), dir);
  282. return FALLBACK_FILESTORE_ATTRIBUTES;
  283. }
  284. CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
  285. .supplyAsync(() -> {
  286. Lock lock = locks.computeIfAbsent(s,
  287. l -> new ReentrantLock());
  288. if (!lock.tryLock()) {
  289. LOG.debug(
  290. "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
  291. Thread.currentThread(), dir);
  292. return Optional.empty();
  293. }
  294. Optional<FileStoreAttributes> attributes = Optional
  295. .empty();
  296. try {
  297. // Some earlier future might have set the value
  298. // and removed itself since we checked for the
  299. // value above. Hence check cache again.
  300. FileStoreAttributes c = attributeCache
  301. .get(s);
  302. if (c != null) {
  303. return Optional.of(c);
  304. }
  305. attributes = readFromConfig(s);
  306. if (attributes.isPresent()) {
  307. attributeCache.put(s, attributes.get());
  308. return attributes;
  309. }
  310. Optional<Duration> resolution = measureFsTimestampResolution(
  311. s, dir);
  312. if (resolution.isPresent()) {
  313. c = new FileStoreAttributes(
  314. resolution.get());
  315. attributeCache.put(s, c);
  316. // for high timestamp resolution measure
  317. // minimal racy interval
  318. if (c.fsTimestampResolution
  319. .toNanos() < 100_000_000L) {
  320. c.minimalRacyInterval = measureMinimalRacyInterval(
  321. dir);
  322. }
  323. if (LOG.isDebugEnabled()) {
  324. LOG.debug(c.toString());
  325. }
  326. saveToConfig(s, c);
  327. }
  328. attributes = Optional.of(c);
  329. } finally {
  330. lock.unlock();
  331. locks.remove(s);
  332. }
  333. return attributes;
  334. });
  335. f = f.exceptionally(e -> {
  336. LOG.error(e.getLocalizedMessage(), e);
  337. return Optional.empty();
  338. });
  339. // even if measuring in background wait a little - if the result
  340. // arrives, it's better than returning the large fallback
  341. Optional<FileStoreAttributes> d = background.get() ? f.get(
  342. 100, TimeUnit.MILLISECONDS) : f.get();
  343. if (d.isPresent()) {
  344. return d.get();
  345. }
  346. // return fallback until measurement is finished
  347. } catch (IOException | InterruptedException
  348. | ExecutionException | CancellationException e) {
  349. LOG.error(e.getMessage(), e);
  350. } catch (TimeoutException | SecurityException e) {
  351. // use fallback
  352. }
  353. LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$
  354. Thread.currentThread(), dir);
  355. return FALLBACK_FILESTORE_ATTRIBUTES;
  356. }
  357. @SuppressWarnings("boxing")
  358. private static Duration measureMinimalRacyInterval(Path dir) {
  359. LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
  360. Thread.currentThread(), dir);
  361. int n = 0;
  362. int failures = 0;
  363. long racyNanos = 0;
  364. ArrayList<Long> deltas = new ArrayList<>();
  365. Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  366. Instant end = Instant.now().plusSeconds(3);
  367. try {
  368. Files.createFile(probe);
  369. do {
  370. n++;
  371. write(probe, "a"); //$NON-NLS-1$
  372. FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
  373. read(probe);
  374. write(probe, "b"); //$NON-NLS-1$
  375. if (!snapshot.isModified(probe.toFile())) {
  376. deltas.add(Long.valueOf(snapshot.lastDelta()));
  377. racyNanos = snapshot.lastRacyThreshold();
  378. failures++;
  379. }
  380. } while (Instant.now().compareTo(end) < 0);
  381. } catch (IOException e) {
  382. LOG.error(e.getMessage(), e);
  383. return FALLBACK_MIN_RACY_INTERVAL;
  384. } finally {
  385. deleteProbe(probe);
  386. }
  387. if (failures > 0) {
  388. Stats stats = new Stats();
  389. for (Long d : deltas) {
  390. stats.add(d);
  391. }
  392. LOG.debug(
  393. "delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$
  394. + "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$
  395. + " delta max [ns], delta avg [ns]," //$NON-NLS-1$
  396. + " delta stddev [ns]\n" //$NON-NLS-1$
  397. + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
  398. n, failures, racyNanos, stats.min(), stats.max(),
  399. stats.avg(), stats.stddev());
  400. return Duration
  401. .ofNanos(Double.valueOf(stats.max()).longValue());
  402. }
  403. // since no failures occurred using the measured filesystem
  404. // timestamp resolution there is no need for minimal racy interval
  405. LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$
  406. Thread.currentThread());
  407. return Duration.ZERO;
  408. }
  409. private static void write(Path p, String body) throws IOException {
  410. FileUtils.mkdirs(p.getParent().toFile(), true);
  411. try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
  412. UTF_8)) {
  413. w.write(body);
  414. }
  415. }
  416. private static String read(Path p) throws IOException {
  417. final byte[] body = IO.readFully(p.toFile());
  418. return new String(body, 0, body.length, UTF_8);
  419. }
  420. private static Optional<Duration> measureFsTimestampResolution(
  421. FileStore s, Path dir) {
  422. LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
  423. Thread.currentThread(), s, dir);
  424. Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
  425. try {
  426. Files.createFile(probe);
  427. FileTime t1 = Files.getLastModifiedTime(probe);
  428. FileTime t2 = t1;
  429. Instant t1i = t1.toInstant();
  430. for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) {
  431. Files.setLastModifiedTime(probe,
  432. FileTime.from(t1i.plusNanos(i * 1000)));
  433. t2 = Files.getLastModifiedTime(probe);
  434. }
  435. Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant());
  436. Duration clockResolution = measureClockResolution();
  437. fsResolution = fsResolution.plus(clockResolution);
  438. LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$
  439. Thread.currentThread(), s, dir);
  440. return Optional.of(fsResolution);
  441. } catch (AccessDeniedException e) {
  442. LOG.warn(e.getLocalizedMessage(), e); // see bug 548648
  443. } catch (IOException e) {
  444. LOG.error(e.getLocalizedMessage(), e);
  445. } finally {
  446. deleteProbe(probe);
  447. }
  448. return Optional.empty();
  449. }
  450. private static Duration measureClockResolution() {
  451. Duration clockResolution = Duration.ZERO;
  452. for (int i = 0; i < 10; i++) {
  453. Instant t1 = Instant.now();
  454. Instant t2 = t1;
  455. while (t2.compareTo(t1) <= 0) {
  456. t2 = Instant.now();
  457. }
  458. Duration r = Duration.between(t1, t2);
  459. if (r.compareTo(clockResolution) > 0) {
  460. clockResolution = r;
  461. }
  462. }
  463. return clockResolution;
  464. }
  465. private static void deleteProbe(Path probe) {
  466. try {
  467. FileUtils.delete(probe.toFile(),
  468. FileUtils.SKIP_MISSING | FileUtils.RETRY);
  469. } catch (IOException e) {
  470. LOG.error(e.getMessage(), e);
  471. }
  472. }
  473. private static Optional<FileStoreAttributes> readFromConfig(
  474. FileStore s) {
  475. StoredConfig userConfig;
  476. try {
  477. userConfig = SystemReader.getInstance().getUserConfig();
  478. } catch (IOException | ConfigInvalidException e) {
  479. LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
  480. return Optional.empty();
  481. }
  482. String key = getConfigKey(s);
  483. Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
  484. ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  485. ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  486. UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  487. if (UNDEFINED_DURATION.equals(resolution)) {
  488. return Optional.empty();
  489. }
  490. Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
  491. ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  492. ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  493. UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
  494. FileStoreAttributes c = new FileStoreAttributes(resolution);
  495. if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
  496. c.minimalRacyInterval = minRacyThreshold;
  497. }
  498. return Optional.of(c);
  499. }
  500. private static void saveToConfig(FileStore s,
  501. FileStoreAttributes c) {
  502. StoredConfig userConfig;
  503. try {
  504. userConfig = SystemReader.getInstance().getUserConfig();
  505. } catch (IOException | ConfigInvalidException e) {
  506. LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
  507. return;
  508. }
  509. long resolution = c.getFsTimestampResolution().toNanos();
  510. TimeUnit resolutionUnit = getUnit(resolution);
  511. long resolutionValue = resolutionUnit.convert(resolution,
  512. TimeUnit.NANOSECONDS);
  513. long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
  514. TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
  515. long minRacyThresholdValue = minRacyThresholdUnit
  516. .convert(minRacyThreshold, TimeUnit.NANOSECONDS);
  517. final int max_retries = 5;
  518. int retries = 0;
  519. boolean succeeded = false;
  520. String key = getConfigKey(s);
  521. while (!succeeded && retries < max_retries) {
  522. try {
  523. userConfig.load();
  524. userConfig.setString(
  525. ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  526. ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
  527. String.format("%d %s", //$NON-NLS-1$
  528. Long.valueOf(resolutionValue),
  529. resolutionUnit.name().toLowerCase()));
  530. userConfig.setString(
  531. ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
  532. ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
  533. String.format("%d %s", //$NON-NLS-1$
  534. Long.valueOf(minRacyThresholdValue),
  535. minRacyThresholdUnit.name().toLowerCase()));
  536. userConfig.save();
  537. succeeded = true;
  538. } catch (LockFailedException e) {
  539. // race with another thread, wait a bit and try again
  540. try {
  541. retries++;
  542. if (retries < max_retries) {
  543. Thread.sleep(100);
  544. LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$
  545. userConfig, Integer.valueOf(retries),
  546. Integer.valueOf(max_retries));
  547. } else {
  548. LOG.warn(MessageFormat.format(
  549. JGitText.get().lockFailedRetry, userConfig,
  550. Integer.valueOf(retries)));
  551. }
  552. } catch (InterruptedException e1) {
  553. Thread.currentThread().interrupt();
  554. break;
  555. }
  556. } catch (IOException e) {
  557. LOG.error(MessageFormat.format(
  558. JGitText.get().cannotSaveConfig, userConfig), e);
  559. break;
  560. } catch (ConfigInvalidException e) {
  561. LOG.error(MessageFormat.format(
  562. JGitText.get().repositoryConfigFileInvalid,
  563. userConfig, e.getMessage()));
  564. break;
  565. }
  566. }
  567. }
  568. private static String getConfigKey(FileStore s) {
  569. final String storeKey;
  570. if (SystemReader.getInstance().isWindows()) {
  571. Object attribute = null;
  572. try {
  573. attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
  574. } catch (IOException ignored) {
  575. // ignore
  576. }
  577. if (attribute instanceof Integer) {
  578. storeKey = attribute.toString();
  579. } else {
  580. storeKey = s.name();
  581. }
  582. } else {
  583. storeKey = s.name();
  584. }
  585. return javaVersionPrefix + storeKey;
  586. }
  587. private static TimeUnit getUnit(long nanos) {
  588. TimeUnit unit;
  589. if (nanos < 200_000L) {
  590. unit = TimeUnit.NANOSECONDS;
  591. } else if (nanos < 200_000_000L) {
  592. unit = TimeUnit.MICROSECONDS;
  593. } else {
  594. unit = TimeUnit.MILLISECONDS;
  595. }
  596. return unit;
  597. }
  598. private final @NonNull Duration fsTimestampResolution;
  599. private Duration minimalRacyInterval;
  600. /**
  601. * @return the measured minimal interval after a file has been modified
  602. * in which we cannot rely on lastModified to detect
  603. * modifications
  604. */
  605. public Duration getMinimalRacyInterval() {
  606. return minimalRacyInterval;
  607. }
  608. /**
  609. * @return the measured filesystem timestamp resolution
  610. */
  611. @NonNull
  612. public Duration getFsTimestampResolution() {
  613. return fsTimestampResolution;
  614. }
  615. /**
  616. * Construct a FileStoreAttributeCache entry for the given filesystem
  617. * timestamp resolution
  618. *
  619. * @param fsTimestampResolution
  620. */
  621. public FileStoreAttributes(
  622. @NonNull Duration fsTimestampResolution) {
  623. this.fsTimestampResolution = fsTimestampResolution;
  624. this.minimalRacyInterval = Duration.ZERO;
  625. }
  626. @SuppressWarnings({ "nls", "boxing" })
  627. @Override
  628. public String toString() {
  629. return String.format(
  630. "FileStoreAttributes[fsTimestampResolution=%,d µs, "
  631. + "minimalRacyInterval=%,d µs]",
  632. fsTimestampResolution.toNanos() / 1000,
  633. minimalRacyInterval.toNanos() / 1000);
  634. }
  635. }
  636. /** The auto-detected implementation selected for this operating system and JRE. */
  637. public static final FS DETECTED = detect();
  638. private volatile static FSFactory factory;
  639. /**
  640. * Auto-detect the appropriate file system abstraction.
  641. *
  642. * @return detected file system abstraction
  643. */
  644. public static FS detect() {
  645. return detect(null);
  646. }
  647. /**
  648. * Whether FileStore attributes should be determined asynchronously
  649. *
  650. * @param asynch
  651. * whether FileStore attributes should be determined
  652. * asynchronously. If false access to cached attributes may block
  653. * for some seconds for the first call per FileStore
  654. * @since 5.1.9
  655. */
  656. public static void setAsyncFileStoreAttributes(boolean asynch) {
  657. FileStoreAttributes.setBackground(asynch);
  658. }
  659. /**
  660. * Auto-detect the appropriate file system abstraction, taking into account
  661. * the presence of a Cygwin installation on the system. Using jgit in
  662. * combination with Cygwin requires a more elaborate (and possibly slower)
  663. * resolution of file system paths.
  664. *
  665. * @param cygwinUsed
  666. * <ul>
  667. * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  668. * combination with jgit</li>
  669. * <li><code>Boolean.FALSE</code> to assume that Cygwin is
  670. * <b>not</b> used with jgit</li>
  671. * <li><code>null</code> to auto-detect whether a Cygwin
  672. * installation is present on the system and in this case assume
  673. * that Cygwin is used</li>
  674. * </ul>
  675. *
  676. * Note: this parameter is only relevant on Windows.
  677. * @return detected file system abstraction
  678. */
  679. public static FS detect(Boolean cygwinUsed) {
  680. if (factory == null) {
  681. factory = new FS.FSFactory();
  682. }
  683. return factory.detect(cygwinUsed);
  684. }
  685. /**
  686. * Get cached FileStore attributes, if not yet available measure them using
  687. * a probe file under the given directory.
  688. *
  689. * @param dir
  690. * the directory under which the probe file will be created to
  691. * measure the timer resolution.
  692. * @return measured filesystem timestamp resolution
  693. * @since 5.1.9
  694. */
  695. public static FileStoreAttributes getFileStoreAttributes(
  696. @NonNull Path dir) {
  697. return FileStoreAttributes.get(dir);
  698. }
  699. private volatile Holder<File> userHome;
  700. private volatile Holder<File> gitSystemConfig;
  701. /**
  702. * Constructs a file system abstraction.
  703. */
  704. protected FS() {
  705. // Do nothing by default.
  706. }
  707. /**
  708. * Initialize this FS using another's current settings.
  709. *
  710. * @param src
  711. * the source FS to copy from.
  712. */
  713. protected FS(FS src) {
  714. userHome = src.userHome;
  715. gitSystemConfig = src.gitSystemConfig;
  716. }
  717. /**
  718. * Create a new instance of the same type of FS.
  719. *
  720. * @return a new instance of the same type of FS.
  721. */
  722. public abstract FS newInstance();
  723. /**
  724. * Does this operating system and JRE support the execute flag on files?
  725. *
  726. * @return true if this implementation can provide reasonably accurate
  727. * executable bit information; false otherwise.
  728. */
  729. public abstract boolean supportsExecute();
  730. /**
  731. * Does this file system support atomic file creation via
  732. * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
  733. * not guaranteed that when two file system clients run createNewFile() in
  734. * parallel only one will succeed. In such cases both clients may think they
  735. * created a new file.
  736. *
  737. * @return true if this implementation support atomic creation of new Files
  738. * by {@link java.io.File#createNewFile()}
  739. * @since 4.5
  740. */
  741. public boolean supportsAtomicCreateNewFile() {
  742. return true;
  743. }
  744. /**
  745. * Does this operating system and JRE supports symbolic links. The
  746. * capability to handle symbolic links is detected at runtime.
  747. *
  748. * @return true if symbolic links may be used
  749. * @since 3.0
  750. */
  751. public boolean supportsSymlinks() {
  752. return false;
  753. }
  754. /**
  755. * Is this file system case sensitive
  756. *
  757. * @return true if this implementation is case sensitive
  758. */
  759. public abstract boolean isCaseSensitive();
  760. /**
  761. * Determine if the file is executable (or not).
  762. * <p>
  763. * Not all platforms and JREs support executable flags on files. If the
  764. * feature is unsupported this method will always return false.
  765. * <p>
  766. * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  767. * this method returns false, rather than the state of the executable flags
  768. * on the target file.</em>
  769. *
  770. * @param f
  771. * abstract path to test.
  772. * @return true if the file is believed to be executable by the user.
  773. */
  774. public abstract boolean canExecute(File f);
  775. /**
  776. * Set a file to be executable by the user.
  777. * <p>
  778. * Not all platforms and JREs support executable flags on files. If the
  779. * feature is unsupported this method will always return false and no
  780. * changes will be made to the file specified.
  781. *
  782. * @param f
  783. * path to modify the executable status of.
  784. * @param canExec
  785. * true to enable execution; false to disable it.
  786. * @return true if the change succeeded; false otherwise.
  787. */
  788. public abstract boolean setExecute(File f, boolean canExec);
  789. /**
  790. * Get the last modified time of a file system object. If the OS/JRE support
  791. * symbolic links, the modification time of the link is returned, rather
  792. * than that of the link target.
  793. *
  794. * @param f
  795. * a {@link java.io.File} object.
  796. * @return last modified time of f
  797. * @throws java.io.IOException
  798. * @since 3.0
  799. * @deprecated use {@link #lastModifiedInstant(Path)} instead
  800. */
  801. @Deprecated
  802. public long lastModified(File f) throws IOException {
  803. return FileUtils.lastModified(f);
  804. }
  805. /**
  806. * Get the last modified time of a file system object. If the OS/JRE support
  807. * symbolic links, the modification time of the link is returned, rather
  808. * than that of the link target.
  809. *
  810. * @param p
  811. * a {@link Path} object.
  812. * @return last modified time of p
  813. * @since 5.1.9
  814. */
  815. public Instant lastModifiedInstant(Path p) {
  816. return FileUtils.lastModifiedInstant(p);
  817. }
  818. /**
  819. * Get the last modified time of a file system object. If the OS/JRE support
  820. * symbolic links, the modification time of the link is returned, rather
  821. * than that of the link target.
  822. *
  823. * @param f
  824. * a {@link File} object.
  825. * @return last modified time of p
  826. * @since 5.1.9
  827. */
  828. public Instant lastModifiedInstant(File f) {
  829. return FileUtils.lastModifiedInstant(f.toPath());
  830. }
  831. /**
  832. * Set the last modified time of a file system object. If the OS/JRE support
  833. * symbolic links, the link is modified, not the target,
  834. *
  835. * @param f
  836. * a {@link java.io.File} object.
  837. * @param time
  838. * last modified time
  839. * @throws java.io.IOException
  840. * @since 3.0
  841. * @deprecated use {@link #setLastModified(Path, Instant)} instead
  842. */
  843. @Deprecated
  844. public void setLastModified(File f, long time) throws IOException {
  845. FileUtils.setLastModified(f, time);
  846. }
  847. /**
  848. * Set the last modified time of a file system object. If the OS/JRE support
  849. * symbolic links, the link is modified, not the target,
  850. *
  851. * @param p
  852. * a {@link Path} object.
  853. * @param time
  854. * last modified time
  855. * @throws java.io.IOException
  856. * @since 5.1.9
  857. */
  858. public void setLastModified(Path p, Instant time) throws IOException {
  859. FileUtils.setLastModified(p, time);
  860. }
  861. /**
  862. * Get the length of a file or link, If the OS/JRE supports symbolic links
  863. * it's the length of the link, else the length of the target.
  864. *
  865. * @param path
  866. * a {@link java.io.File} object.
  867. * @return length of a file
  868. * @throws java.io.IOException
  869. * @since 3.0
  870. */
  871. public long length(File path) throws IOException {
  872. return FileUtils.getLength(path);
  873. }
  874. /**
  875. * Delete a file. Throws an exception if delete fails.
  876. *
  877. * @param f
  878. * a {@link java.io.File} object.
  879. * @throws java.io.IOException
  880. * this may be a Java7 subclass with detailed information
  881. * @since 3.3
  882. */
  883. public void delete(File f) throws IOException {
  884. FileUtils.delete(f);
  885. }
  886. /**
  887. * Resolve this file to its actual path name that the JRE can use.
  888. * <p>
  889. * This method can be relatively expensive. Computing a translation may
  890. * require forking an external process per path name translated. Callers
  891. * should try to minimize the number of translations necessary by caching
  892. * the results.
  893. * <p>
  894. * Not all platforms and JREs require path name translation. Currently only
  895. * Cygwin on Win32 require translation for Cygwin based paths.
  896. *
  897. * @param dir
  898. * directory relative to which the path name is.
  899. * @param name
  900. * path name to translate.
  901. * @return the translated path. <code>new File(dir,name)</code> if this
  902. * platform does not require path name translation.
  903. */
  904. public File resolve(File dir, String name) {
  905. final File abspn = new File(name);
  906. if (abspn.isAbsolute())
  907. return abspn;
  908. return new File(dir, name);
  909. }
  910. /**
  911. * Determine the user's home directory (location where preferences are).
  912. * <p>
  913. * This method can be expensive on the first invocation if path name
  914. * translation is required. Subsequent invocations return a cached result.
  915. * <p>
  916. * Not all platforms and JREs require path name translation. Currently only
  917. * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  918. *
  919. * @return the user's home directory; null if the user does not have one.
  920. */
  921. public File userHome() {
  922. Holder<File> p = userHome;
  923. if (p == null) {
  924. p = new Holder<>(userHomeImpl());
  925. userHome = p;
  926. }
  927. return p.value;
  928. }
  929. /**
  930. * Set the user's home directory location.
  931. *
  932. * @param path
  933. * the location of the user's preferences; null if there is no
  934. * home directory for the current user.
  935. * @return {@code this}.
  936. */
  937. public FS setUserHome(File path) {
  938. userHome = new Holder<>(path);
  939. return this;
  940. }
  941. /**
  942. * Does this file system have problems with atomic renames?
  943. *
  944. * @return true if the caller should retry a failed rename of a lock file.
  945. */
  946. public abstract boolean retryFailedLockFileCommit();
  947. /**
  948. * Return all the attributes of a file, without following symbolic links.
  949. *
  950. * @param file
  951. * @return {@link BasicFileAttributes} of the file
  952. * @throws IOException in case of any I/O errors accessing the file
  953. *
  954. * @since 4.5.6
  955. */
  956. public BasicFileAttributes fileAttributes(File file) throws IOException {
  957. return FileUtils.fileAttributes(file);
  958. }
  959. /**
  960. * Determine the user's home directory (location where preferences are).
  961. *
  962. * @return the user's home directory; null if the user does not have one.
  963. */
  964. protected File userHomeImpl() {
  965. final String home = AccessController.doPrivileged(
  966. (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
  967. );
  968. if (home == null || home.length() == 0)
  969. return null;
  970. return new File(home).getAbsoluteFile();
  971. }
  972. /**
  973. * Searches the given path to see if it contains one of the given files.
  974. * Returns the first it finds. Returns null if not found or if path is null.
  975. *
  976. * @param path
  977. * List of paths to search separated by File.pathSeparator
  978. * @param lookFor
  979. * Files to search for in the given path
  980. * @return the first match found, or null
  981. * @since 3.0
  982. */
  983. protected static File searchPath(String path, String... lookFor) {
  984. if (path == null)
  985. return null;
  986. for (String p : path.split(File.pathSeparator)) {
  987. for (String command : lookFor) {
  988. final File e = new File(p, command);
  989. if (e.isFile())
  990. return e.getAbsoluteFile();
  991. }
  992. }
  993. return null;
  994. }
  995. /**
  996. * Execute a command and return a single line of output as a String
  997. *
  998. * @param dir
  999. * Working directory for the command
  1000. * @param command
  1001. * as component array
  1002. * @param encoding
  1003. * to be used to parse the command's output
  1004. * @return the one-line output of the command or {@code null} if there is
  1005. * none
  1006. * @throws org.eclipse.jgit.errors.CommandFailedException
  1007. * thrown when the command failed (return code was non-zero)
  1008. */
  1009. @Nullable
  1010. protected static String readPipe(File dir, String[] command,
  1011. String encoding) throws CommandFailedException {
  1012. return readPipe(dir, command, encoding, null);
  1013. }
  1014. /**
  1015. * Execute a command and return a single line of output as a String
  1016. *
  1017. * @param dir
  1018. * Working directory for the command
  1019. * @param command
  1020. * as component array
  1021. * @param encoding
  1022. * to be used to parse the command's output
  1023. * @param env
  1024. * Map of environment variables to be merged with those of the
  1025. * current process
  1026. * @return the one-line output of the command or {@code null} if there is
  1027. * none
  1028. * @throws org.eclipse.jgit.errors.CommandFailedException
  1029. * thrown when the command failed (return code was non-zero)
  1030. * @since 4.0
  1031. */
  1032. @Nullable
  1033. protected static String readPipe(File dir, String[] command,
  1034. String encoding, Map<String, String> env)
  1035. throws CommandFailedException {
  1036. final boolean debug = LOG.isDebugEnabled();
  1037. try {
  1038. if (debug) {
  1039. LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  1040. + dir);
  1041. }
  1042. ProcessBuilder pb = new ProcessBuilder(command);
  1043. pb.directory(dir);
  1044. if (env != null) {
  1045. pb.environment().putAll(env);
  1046. }
  1047. Process p;
  1048. try {
  1049. p = pb.start();
  1050. } catch (IOException e) {
  1051. // Process failed to start
  1052. throw new CommandFailedException(-1, e.getMessage(), e);
  1053. }
  1054. p.getOutputStream().close();
  1055. GobblerThread gobbler = new GobblerThread(p, command, dir);
  1056. gobbler.start();
  1057. String r = null;
  1058. try (BufferedReader lineRead = new BufferedReader(
  1059. new InputStreamReader(p.getInputStream(), encoding))) {
  1060. r = lineRead.readLine();
  1061. if (debug) {
  1062. LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  1063. LOG.debug("remaining output:\n"); //$NON-NLS-1$
  1064. String l;
  1065. while ((l = lineRead.readLine()) != null) {
  1066. LOG.debug(l);
  1067. }
  1068. }
  1069. }
  1070. for (;;) {
  1071. try {
  1072. int rc = p.waitFor();
  1073. gobbler.join();
  1074. if (rc == 0 && !gobbler.fail.get()) {
  1075. return r;
  1076. } else {
  1077. if (debug) {
  1078. LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  1079. }
  1080. throw new CommandFailedException(rc,
  1081. gobbler.errorMessage.get(),
  1082. gobbler.exception.get());
  1083. }
  1084. } catch (InterruptedException ie) {
  1085. // Stop bothering me, I have a zombie to reap.
  1086. }
  1087. }
  1088. } catch (IOException e) {
  1089. LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  1090. }
  1091. if (debug) {
  1092. LOG.debug("readpipe returns null"); //$NON-NLS-1$
  1093. }
  1094. return null;
  1095. }
  1096. private static class GobblerThread extends Thread {
  1097. /* The process has 5 seconds to exit after closing stderr */
  1098. private static final int PROCESS_EXIT_TIMEOUT = 5;
  1099. private final Process p;
  1100. private final String desc;
  1101. private final String dir;
  1102. final AtomicBoolean fail = new AtomicBoolean();
  1103. final AtomicReference<String> errorMessage = new AtomicReference<>();
  1104. final AtomicReference<Throwable> exception = new AtomicReference<>();
  1105. GobblerThread(Process p, String[] command, File dir) {
  1106. this.p = p;
  1107. this.desc = Arrays.toString(command);
  1108. this.dir = Objects.toString(dir);
  1109. }
  1110. @Override
  1111. public void run() {
  1112. StringBuilder err = new StringBuilder();
  1113. try (InputStream is = p.getErrorStream()) {
  1114. int ch;
  1115. while ((ch = is.read()) != -1) {
  1116. err.append((char) ch);
  1117. }
  1118. } catch (IOException e) {
  1119. if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  1120. setError(e, e.getMessage(), p.exitValue());
  1121. fail.set(true);
  1122. } else {
  1123. // ignore. command terminated faster and stream was just closed
  1124. // or the process didn't terminate within timeout
  1125. }
  1126. } finally {
  1127. if (waitForProcessCompletion(null) && err.length() > 0) {
  1128. setError(null, err.toString(), p.exitValue());
  1129. if (p.exitValue() != 0) {
  1130. fail.set(true);
  1131. }
  1132. }
  1133. }
  1134. }
  1135. @SuppressWarnings("boxing")
  1136. private boolean waitForProcessCompletion(IOException originalError) {
  1137. try {
  1138. if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  1139. setError(originalError, MessageFormat.format(
  1140. JGitText.get().commandClosedStderrButDidntExit,
  1141. desc, PROCESS_EXIT_TIMEOUT), -1);
  1142. fail.set(true);
  1143. return false;
  1144. }
  1145. } catch (InterruptedException e) {
  1146. setError(originalError, MessageFormat.format(
  1147. JGitText.get().threadInterruptedWhileRunning, desc), -1);
  1148. fail.set(true);
  1149. return false;
  1150. }
  1151. return true;
  1152. }
  1153. private void setError(IOException e, String message, int exitCode) {
  1154. exception.set(e);
  1155. errorMessage.set(MessageFormat.format(
  1156. JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  1157. desc, dir, Integer.valueOf(exitCode), message));
  1158. }
  1159. }
  1160. /**
  1161. * Discover the path to the Git executable.
  1162. *
  1163. * @return the path to the Git executable or {@code null} if it cannot be
  1164. * determined.
  1165. * @since 4.0
  1166. */
  1167. protected abstract File discoverGitExe();
  1168. /**
  1169. * Discover the path to the system-wide Git configuration file
  1170. *
  1171. * @return the path to the system-wide Git configuration file or
  1172. * {@code null} if it cannot be determined.
  1173. * @since 4.0
  1174. */
  1175. protected File discoverGitSystemConfig() {
  1176. File gitExe = discoverGitExe();
  1177. if (gitExe == null) {
  1178. return null;
  1179. }
  1180. // Bug 480782: Check if the discovered git executable is JGit CLI
  1181. String v;
  1182. try {
  1183. v = readPipe(gitExe.getParentFile(),
  1184. new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
  1185. Charset.defaultCharset().name());
  1186. } catch (CommandFailedException e) {
  1187. LOG.warn(e.getMessage());
  1188. return null;
  1189. }
  1190. if (StringUtils.isEmptyOrNull(v)
  1191. || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  1192. return null;
  1193. }
  1194. // Trick Git into printing the path to the config file by using "echo"
  1195. // as the editor.
  1196. Map<String, String> env = new HashMap<>();
  1197. env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
  1198. String w;
  1199. try {
  1200. w = readPipe(gitExe.getParentFile(),
  1201. new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  1202. Charset.defaultCharset().name(), env);
  1203. } catch (CommandFailedException e) {
  1204. LOG.warn(e.getMessage());
  1205. return null;
  1206. }
  1207. if (StringUtils.isEmptyOrNull(w)) {
  1208. return null;
  1209. }
  1210. return new File(w);
  1211. }
  1212. /**
  1213. * Get the currently used path to the system-wide Git configuration file.
  1214. *
  1215. * @return the currently used path to the system-wide Git configuration file
  1216. * or {@code null} if none has been set.
  1217. * @since 4.0
  1218. */
  1219. public File getGitSystemConfig() {
  1220. if (gitSystemConfig == null) {
  1221. gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  1222. }
  1223. return gitSystemConfig.value;
  1224. }
  1225. /**
  1226. * Set the path to the system-wide Git configuration file to use.
  1227. *
  1228. * @param configFile
  1229. * the path to the config file.
  1230. * @return {@code this}
  1231. * @since 4.0
  1232. */
  1233. public FS setGitSystemConfig(File configFile) {
  1234. gitSystemConfig = new Holder<>(configFile);
  1235. return this;
  1236. }
  1237. /**
  1238. * Get the parent directory of this file's parent directory
  1239. *
  1240. * @param grandchild
  1241. * a {@link java.io.File} object.
  1242. * @return the parent directory of this file's parent directory or
  1243. * {@code null} in case there's no grandparent directory
  1244. * @since 4.0
  1245. */
  1246. protected static File resolveGrandparentFile(File grandchild) {
  1247. if (grandchild != null) {
  1248. File parent = grandchild.getParentFile();
  1249. if (parent != null)
  1250. return parent.getParentFile();
  1251. }
  1252. return null;
  1253. }
  1254. /**
  1255. * Check if a file is a symbolic link and read it
  1256. *
  1257. * @param path
  1258. * a {@link java.io.File} object.
  1259. * @return target of link or null
  1260. * @throws java.io.IOException
  1261. * @since 3.0
  1262. */
  1263. public String readSymLink(File path) throws IOException {
  1264. return FileUtils.readSymLink(path);
  1265. }
  1266. /**
  1267. * Whether the path is a symbolic link (and we support these).
  1268. *
  1269. * @param path
  1270. * a {@link java.io.File} object.
  1271. * @return true if the path is a symbolic link (and we support these)
  1272. * @throws java.io.IOException
  1273. * @since 3.0
  1274. */
  1275. public boolean isSymLink(File path) throws IOException {
  1276. return FileUtils.isSymlink(path);
  1277. }
  1278. /**
  1279. * Tests if the path exists, in case of a symbolic link, true even if the
  1280. * target does not exist
  1281. *
  1282. * @param path
  1283. * a {@link java.io.File} object.
  1284. * @return true if path exists
  1285. * @since 3.0
  1286. */
  1287. public boolean exists(File path) {
  1288. return FileUtils.exists(path);
  1289. }
  1290. /**
  1291. * Check if path is a directory. If the OS/JRE supports symbolic links and
  1292. * path is a symbolic link to a directory, this method returns false.
  1293. *
  1294. * @param path
  1295. * a {@link java.io.File} object.
  1296. * @return true if file is a directory,
  1297. * @since 3.0
  1298. */
  1299. public boolean isDirectory(File path) {
  1300. return FileUtils.isDirectory(path);
  1301. }
  1302. /**
  1303. * Examine if path represents a regular file. If the OS/JRE supports
  1304. * symbolic links the test returns false if path represents a symbolic link.
  1305. *
  1306. * @param path
  1307. * a {@link java.io.File} object.
  1308. * @return true if path represents a regular file
  1309. * @since 3.0
  1310. */
  1311. public boolean isFile(File path) {
  1312. return FileUtils.isFile(path);
  1313. }
  1314. /**
  1315. * Whether path is hidden, either starts with . on unix or has the hidden
  1316. * attribute in windows
  1317. *
  1318. * @param path
  1319. * a {@link java.io.File} object.
  1320. * @return true if path is hidden, either starts with . on unix or has the
  1321. * hidden attribute in windows
  1322. * @throws java.io.IOException
  1323. * @since 3.0
  1324. */
  1325. public boolean isHidden(File path) throws IOException {
  1326. return FileUtils.isHidden(path);
  1327. }
  1328. /**
  1329. * Set the hidden attribute for file whose name starts with a period.
  1330. *
  1331. * @param path
  1332. * a {@link java.io.File} object.
  1333. * @param hidden
  1334. * whether to set the file hidden
  1335. * @throws java.io.IOException
  1336. * @since 3.0
  1337. */
  1338. public void setHidden(File path, boolean hidden) throws IOException {
  1339. FileUtils.setHidden(path, hidden);
  1340. }
  1341. /**
  1342. * Create a symbolic link
  1343. *
  1344. * @param path
  1345. * a {@link java.io.File} object.
  1346. * @param target
  1347. * target path of the symlink
  1348. * @throws java.io.IOException
  1349. * @since 3.0
  1350. */
  1351. public void createSymLink(File path, String target) throws IOException {
  1352. FileUtils.createSymLink(path, target);
  1353. }
  1354. /**
  1355. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1356. * of this class may take care to provide a safe implementation for this
  1357. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1358. *
  1359. * @param path
  1360. * the file to be created
  1361. * @return <code>true</code> if the file was created, <code>false</code> if
  1362. * the file already existed
  1363. * @throws java.io.IOException
  1364. * @deprecated use {@link #createNewFileAtomic(File)} instead
  1365. * @since 4.5
  1366. */
  1367. @Deprecated
  1368. public boolean createNewFile(File path) throws IOException {
  1369. return path.createNewFile();
  1370. }
  1371. /**
  1372. * A token representing a file created by
  1373. * {@link #createNewFileAtomic(File)}. The token must be retained until the
  1374. * file has been deleted in order to guarantee that the unique file was
  1375. * created atomically. As soon as the file is no longer needed the lock
  1376. * token must be closed.
  1377. *
  1378. * @since 4.7
  1379. */
  1380. public static class LockToken implements Closeable {
  1381. private boolean isCreated;
  1382. private Optional<Path> link;
  1383. LockToken(boolean isCreated, Optional<Path> link) {
  1384. this.isCreated = isCreated;
  1385. this.link = link;
  1386. }
  1387. /**
  1388. * @return {@code true} if the file was created successfully
  1389. */
  1390. public boolean isCreated() {
  1391. return isCreated;
  1392. }
  1393. @Override
  1394. public void close() {
  1395. if (!link.isPresent()) {
  1396. return;
  1397. }
  1398. Path p = link.get();
  1399. if (!Files.exists(p)) {
  1400. return;
  1401. }
  1402. try {
  1403. Files.delete(p);
  1404. } catch (IOException e) {
  1405. LOG.error(MessageFormat
  1406. .format(JGitText.get().closeLockTokenFailed, this), e);
  1407. }
  1408. }
  1409. @Override
  1410. public String toString() {
  1411. return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
  1412. ", link=" //$NON-NLS-1$
  1413. + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
  1414. : "<null>]"); //$NON-NLS-1$
  1415. }
  1416. }
  1417. /**
  1418. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1419. * of this class may take care to provide a safe implementation for this
  1420. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1421. *
  1422. * @param path
  1423. * the file to be created
  1424. * @return LockToken this token must be closed after the created file was
  1425. * deleted
  1426. * @throws IOException
  1427. * @since 4.7
  1428. */
  1429. public LockToken createNewFileAtomic(File path) throws IOException {
  1430. return new LockToken(path.createNewFile(), Optional.empty());
  1431. }
  1432. /**
  1433. * See
  1434. * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  1435. *
  1436. * @param base
  1437. * The path against which <code>other</code> should be
  1438. * relativized.
  1439. * @param other
  1440. * The path that will be made relative to <code>base</code>.
  1441. * @return A relative path that, when resolved against <code>base</code>,
  1442. * will yield the original <code>other</code>.
  1443. * @see FileUtils#relativizePath(String, String, String, boolean)
  1444. * @since 3.7
  1445. */
  1446. public String relativize(String base, String other) {
  1447. return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  1448. }
  1449. /**
  1450. * Enumerates children of a directory.
  1451. *
  1452. * @param directory
  1453. * to get the children of
  1454. * @param fileModeStrategy
  1455. * to use to calculate the git mode of a child
  1456. * @return an array of entries for the children
  1457. *
  1458. * @since 5.0
  1459. */
  1460. public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
  1461. final File[] all = directory.listFiles();
  1462. if (all == null) {
  1463. return NO_ENTRIES;
  1464. }
  1465. final Entry[] result = new Entry[all.length];
  1466. for (int i = 0; i < result.length; i++) {
  1467. result[i] = new FileEntry(all[i], this, fileModeStrategy);
  1468. }
  1469. return result;
  1470. }
  1471. /**
  1472. * Checks whether the given hook is defined for the given repository, then
  1473. * runs it with the given arguments.
  1474. * <p>
  1475. * The hook's standard output and error streams will be redirected to
  1476. * <code>System.out</code> and <code>System.err</code> respectively. The
  1477. * hook will have no stdin.
  1478. * </p>
  1479. *
  1480. * @param repository
  1481. * The repository for which a hook should be run.
  1482. * @param hookName
  1483. * The name of the hook to be executed.
  1484. * @param args
  1485. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1486. * but can be an empty array.
  1487. * @return The ProcessResult describing this hook's execution.
  1488. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1489. * if we fail to run the hook somehow. Causes may include an
  1490. * interrupted process or I/O errors.
  1491. * @since 4.0
  1492. */
  1493. public ProcessResult runHookIfPresent(Repository repository,
  1494. final String hookName,
  1495. String[] args) throws JGitInternalException {
  1496. return runHookIfPresent(repository, hookName, args, System.out, System.err,
  1497. null);
  1498. }
  1499. /**
  1500. * Checks whether the given hook is defined for the given repository, then
  1501. * runs it with the given arguments.
  1502. *
  1503. * @param repository
  1504. * The repository for which a hook should be run.
  1505. * @param hookName
  1506. * The name of the hook to be executed.
  1507. * @param args
  1508. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1509. * but can be an empty array.
  1510. * @param outRedirect
  1511. * A print stream on which to redirect the hook's stdout. Can be
  1512. * <code>null</code>, in which case the hook's standard output
  1513. * will be lost.
  1514. * @param errRedirect
  1515. * A print stream on which to redirect the hook's stderr. Can be
  1516. * <code>null</code>, in which case the hook's standard error
  1517. * will be lost.
  1518. * @param stdinArgs
  1519. * A string to pass on to the standard input of the hook. May be
  1520. * <code>null</code>.
  1521. * @return The ProcessResult describing this hook's execution.
  1522. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1523. * if we fail to run the hook somehow. Causes may include an
  1524. * interrupted process or I/O errors.
  1525. * @since 4.0
  1526. */
  1527. public ProcessResult runHookIfPresent(Repository repository,
  1528. final String hookName,
  1529. String[] args, PrintStream outRedirect, PrintStream errRedirect,
  1530. String stdinArgs) throws JGitInternalException {
  1531. return new ProcessResult(Status.NOT_SUPPORTED);
  1532. }
  1533. /**
  1534. * See
  1535. * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
  1536. * . Should only be called by FS supporting shell scripts execution.
  1537. *
  1538. * @param repository
  1539. * The repository for which a hook should be run.
  1540. * @param hookName
  1541. * The name of the hook to be executed.
  1542. * @param args
  1543. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1544. * but can be an empty array.
  1545. * @param outRedirect
  1546. * A print stream on which to redirect the hook's stdout. Can be
  1547. * <code>null</code>, in which case the hook's standard output
  1548. * will be lost.
  1549. * @param errRedirect
  1550. * A print stream on which to redirect the hook's stderr. Can be
  1551. * <code>null</code>, in which case the hook's standard error
  1552. * will be lost.
  1553. * @param stdinArgs
  1554. * A string to pass on to the standard input of the hook. May be
  1555. * <code>null</code>.
  1556. * @return The ProcessResult describing this hook's execution.
  1557. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1558. * if we fail to run the hook somehow. Causes may include an
  1559. * interrupted process or I/O errors.
  1560. * @since 4.0
  1561. */
  1562. protected ProcessResult internalRunHookIfPresent(Repository repository,
  1563. final String hookName, String[] args, PrintStream outRedirect,
  1564. PrintStream errRedirect, String stdinArgs)
  1565. throws JGitInternalException {
  1566. final File hookFile = findHook(repository, hookName);
  1567. if (hookFile == null)
  1568. return new ProcessResult(Status.NOT_PRESENT);
  1569. final String hookPath = hookFile.getAbsolutePath();
  1570. final File runDirectory;
  1571. if (repository.isBare())
  1572. runDirectory = repository.getDirectory();
  1573. else
  1574. runDirectory = repository.getWorkTree();
  1575. final String cmd = relativize(runDirectory.getAbsolutePath(),
  1576. hookPath);
  1577. ProcessBuilder hookProcess = runInShell(cmd, args);
  1578. hookProcess.directory(runDirectory);
  1579. Map<String, String> environment = hookProcess.environment();
  1580. environment.put(Constants.GIT_DIR_KEY,
  1581. repository.getDirectory().getAbsolutePath());
  1582. if (!repository.isBare()) {
  1583. environment.put(Constants.GIT_WORK_TREE_KEY,
  1584. repository.getWorkTree().getAbsolutePath());
  1585. }
  1586. try {
  1587. return new ProcessResult(runProcess(hookProcess, outRedirect,
  1588. errRedirect, stdinArgs), Status.OK);
  1589. } catch (IOException e) {
  1590. throw new JGitInternalException(MessageFormat.format(
  1591. JGitText.get().exceptionCaughtDuringExecutionOfHook,
  1592. hookName), e);
  1593. } catch (InterruptedException e) {
  1594. throw new JGitInternalException(MessageFormat.format(
  1595. JGitText.get().exceptionHookExecutionInterrupted,
  1596. hookName), e);
  1597. }
  1598. }
  1599. /**
  1600. * Tries to find a hook matching the given one in the given repository.
  1601. *
  1602. * @param repository
  1603. * The repository within which to find a hook.
  1604. * @param hookName
  1605. * The name of the hook we're trying to find.
  1606. * @return The {@link java.io.File} containing this particular hook if it
  1607. * exists in the given repository, <code>null</code> otherwise.
  1608. * @since 4.0
  1609. */
  1610. public File findHook(Repository repository, String hookName) {
  1611. File gitDir = repository.getDirectory();
  1612. if (gitDir == null)
  1613. return null;
  1614. final File hookFile = new File(new File(gitDir,
  1615. Constants.HOOKS), hookName);
  1616. return hookFile.isFile() ? hookFile : null;
  1617. }
  1618. /**
  1619. * Runs the given process until termination, clearing its stdout and stderr
  1620. * streams on-the-fly.
  1621. *
  1622. * @param processBuilder
  1623. * The process builder configured for this process.
  1624. * @param outRedirect
  1625. * A OutputStream on which to redirect the processes stdout. Can
  1626. * be <code>null</code>, in which case the processes standard
  1627. * output will be lost.
  1628. * @param errRedirect
  1629. * A OutputStream on which to redirect the processes stderr. Can
  1630. * be <code>null</code>, in which case the processes standard
  1631. * error will be lost.
  1632. * @param stdinArgs
  1633. * A string to pass on to the standard input of the hook. Can be
  1634. * <code>null</code>.
  1635. * @return the exit value of this process.
  1636. * @throws java.io.IOException
  1637. * if an I/O error occurs while executing this process.
  1638. * @throws java.lang.InterruptedException
  1639. * if the current thread is interrupted while waiting for the
  1640. * process to end.
  1641. * @since 4.2
  1642. */
  1643. public int runProcess(ProcessBuilder processBuilder,
  1644. OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  1645. throws IOException, InterruptedException {
  1646. InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  1647. stdinArgs.getBytes(UTF_8));
  1648. return runProcess(processBuilder, outRedirect, errRedirect, in);
  1649. }
  1650. /**
  1651. * Runs the given process until termination, clearing its stdout and stderr
  1652. * streams on-the-fly.
  1653. *
  1654. * @param processBuilder
  1655. * The process builder configured for this process.
  1656. * @param outRedirect
  1657. * An OutputStream on which to redirect the processes stdout. Can
  1658. * be <code>null</code>, in which case the processes standard
  1659. * output will be lost.
  1660. * @param errRedirect
  1661. * An OutputStream on which to redirect the processes stderr. Can
  1662. * be <code>null</code>, in which case the processes standard
  1663. * error will be lost.
  1664. * @param inRedirect
  1665. * An InputStream from which to redirect the processes stdin. Can
  1666. * be <code>null</code>, in which case the process doesn't get
  1667. * any data over stdin. It is assumed that the whole InputStream
  1668. * will be consumed by the process. The method will close the
  1669. * inputstream after all bytes are read.
  1670. * @return the return code of this process.
  1671. * @throws java.io.IOException
  1672. * if an I/O error occurs while executing this process.
  1673. * @throws java.lang.InterruptedException
  1674. * if the current thread is interrupted while waiting for the
  1675. * process to end.
  1676. * @since 4.2
  1677. */
  1678. public int runProcess(ProcessBuilder processBuilder,
  1679. OutputStream outRedirect, OutputStream errRedirect,
  1680. InputStream inRedirect) throws IOException,
  1681. InterruptedException {
  1682. final ExecutorService executor = Executors.newFixedThreadPool(2);
  1683. Process process = null;
  1684. // We'll record the first I/O exception that occurs, but keep on trying
  1685. // to dispose of our open streams and file handles
  1686. IOException ioException = null;
  1687. try {
  1688. process = processBuilder.start();
  1689. executor.execute(
  1690. new StreamGobbler(process.getErrorStream(), errRedirect));
  1691. executor.execute(
  1692. new StreamGobbler(process.getInputStream(), outRedirect));
  1693. @SuppressWarnings("resource") // Closed in the finally block
  1694. OutputStream outputStream = process.getOutputStream();
  1695. try {
  1696. if (inRedirect != null) {
  1697. new StreamGobbler(inRedirect, outputStream).copy();
  1698. }
  1699. } finally {
  1700. try {
  1701. outputStream.close();
  1702. } catch (IOException e) {
  1703. // When the process exits before consuming the input, the OutputStream
  1704. // is replaced with the null output stream. This null output stream
  1705. // throws IOException for all write calls. When StreamGobbler fails to
  1706. // flush the buffer because of this, this close call tries to flush it
  1707. // again. This causes another IOException. Since we ignore the
  1708. // IOException in StreamGobbler, we also ignore the exception here.
  1709. }
  1710. }
  1711. return process.waitFor();
  1712. } catch (IOException e) {
  1713. ioException = e;
  1714. } finally {
  1715. shutdownAndAwaitTermination(executor);
  1716. if (process != null) {
  1717. try {
  1718. process.waitFor();
  1719. } catch (InterruptedException e) {
  1720. // Thrown by the outer try.
  1721. // Swallow this one to carry on our cleanup, and clear the
  1722. // interrupted flag (processes throw the exception without
  1723. // clearing the flag).
  1724. Thread.interrupted();
  1725. }
  1726. // A process doesn't clean its own resources even when destroyed
  1727. // Explicitly try and close all three streams, preserving the
  1728. // outer I/O exception if any.
  1729. if (inRedirect != null) {
  1730. inRedirect.close();
  1731. }
  1732. try {
  1733. process.getErrorStream().close();
  1734. } catch (IOException e) {
  1735. ioException = ioException != null ? ioException : e;
  1736. }
  1737. try {
  1738. process.getInputStream().close();
  1739. } catch (IOException e) {
  1740. ioException = ioException != null ? ioException : e;
  1741. }
  1742. try {
  1743. process.getOutputStream().close();
  1744. } catch (IOException e) {
  1745. ioException = ioException != null ? ioException : e;
  1746. }
  1747. process.destroy();
  1748. }
  1749. }
  1750. // We can only be here if the outer try threw an IOException.
  1751. throw ioException;
  1752. }
  1753. /**
  1754. * Shuts down an {@link ExecutorService} in two phases, first by calling
  1755. * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  1756. * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  1757. * necessary, to cancel any lingering tasks. Returns true if the pool has
  1758. * been properly shutdown, false otherwise.
  1759. * <p>
  1760. *
  1761. * @param pool
  1762. * the pool to shutdown
  1763. * @return <code>true</code> if the pool has been properly shutdown,
  1764. * <code>false</code> otherwise.
  1765. */
  1766. private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  1767. boolean hasShutdown = true;
  1768. pool.shutdown(); // Disable new tasks from being submitted
  1769. try {
  1770. // Wait a while for existing tasks to terminate
  1771. if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  1772. pool.shutdownNow(); // Cancel currently executing tasks
  1773. // Wait a while for tasks to respond to being canceled
  1774. if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  1775. hasShutdown = false;
  1776. }
  1777. } catch (InterruptedException ie) {
  1778. // (Re-)Cancel if current thread also interrupted
  1779. pool.shutdownNow();
  1780. // Preserve interrupt status
  1781. Thread.currentThread().interrupt();
  1782. hasShutdown = false;
  1783. }
  1784. return hasShutdown;
  1785. }
  1786. /**
  1787. * Initialize a ProcessBuilder to run a command using the system shell.
  1788. *
  1789. * @param cmd
  1790. * command to execute. This string should originate from the
  1791. * end-user, and thus is platform specific.
  1792. * @param args
  1793. * arguments to pass to command. These should be protected from
  1794. * shell evaluation.
  1795. * @return a partially completed process builder. Caller should finish
  1796. * populating directory, environment, and then start the process.
  1797. */
  1798. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  1799. /**
  1800. * Execute a command defined by a {@link java.lang.ProcessBuilder}.
  1801. *
  1802. * @param pb
  1803. * The command to be executed
  1804. * @param in
  1805. * The standard input stream passed to the process
  1806. * @return The result of the executed command
  1807. * @throws java.lang.InterruptedException
  1808. * @throws java.io.IOException
  1809. * @since 4.2
  1810. */
  1811. public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  1812. throws IOException, InterruptedException {
  1813. try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  1814. TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
  1815. 1024 * 1024)) {
  1816. int rc = runProcess(pb, stdout, stderr, in);
  1817. return new ExecutionResult(stdout, stderr, rc);
  1818. }
  1819. }
  1820. private static class Holder<V> {
  1821. final V value;
  1822. Holder(V value) {
  1823. this.value = value;
  1824. }
  1825. }
  1826. /**
  1827. * File attributes we typically care for.
  1828. *
  1829. * @since 3.3
  1830. */
  1831. public static class Attributes {
  1832. /**
  1833. * @return true if this are the attributes of a directory
  1834. */
  1835. public boolean isDirectory() {
  1836. return isDirectory;
  1837. }
  1838. /**
  1839. * @return true if this are the attributes of an executable file
  1840. */
  1841. public boolean isExecutable() {
  1842. return isExecutable;
  1843. }
  1844. /**
  1845. * @return true if this are the attributes of a symbolic link
  1846. */
  1847. public boolean isSymbolicLink() {
  1848. return isSymbolicLink;
  1849. }
  1850. /**
  1851. * @return true if this are the attributes of a regular file
  1852. */
  1853. public boolean isRegularFile() {
  1854. return isRegularFile;
  1855. }
  1856. /**
  1857. * @return the time when the file was created
  1858. */
  1859. public long getCreationTime() {
  1860. return creationTime;
  1861. }
  1862. /**
  1863. * @return the time (milliseconds since 1970-01-01) when this object was
  1864. * last modified
  1865. * @deprecated use getLastModifiedInstant instead
  1866. */
  1867. @Deprecated
  1868. public long getLastModifiedTime() {
  1869. return lastModifiedInstant.toEpochMilli();
  1870. }
  1871. /**
  1872. * @return the time when this object was last modified
  1873. * @since 5.1.9
  1874. */
  1875. public Instant getLastModifiedInstant() {
  1876. return lastModifiedInstant;
  1877. }
  1878. private final boolean isDirectory;
  1879. private final boolean isSymbolicLink;
  1880. private final boolean isRegularFile;
  1881. private final long creationTime;
  1882. private final Instant lastModifiedInstant;
  1883. private final boolean isExecutable;
  1884. private final File file;
  1885. private final boolean exists;
  1886. /**
  1887. * file length
  1888. */
  1889. protected long length = -1;
  1890. final FS fs;
  1891. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  1892. boolean isExecutable, boolean isSymbolicLink,
  1893. boolean isRegularFile, long creationTime,
  1894. Instant lastModifiedInstant, long length) {
  1895. this.fs = fs;
  1896. this.file = file;
  1897. this.exists = exists;
  1898. this.isDirectory = isDirectory;
  1899. this.isExecutable = isExecutable;
  1900. this.isSymbolicLink = isSymbolicLink;
  1901. this.isRegularFile = isRegularFile;
  1902. this.creationTime = creationTime;
  1903. this.lastModifiedInstant = lastModifiedInstant;
  1904. this.length = length;
  1905. }
  1906. /**
  1907. * Constructor when there are issues with reading. All attributes except
  1908. * given will be set to the default values.
  1909. *
  1910. * @param fs
  1911. * @param path
  1912. */
  1913. public Attributes(File path, FS fs) {
  1914. this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
  1915. }
  1916. /**
  1917. * @return length of this file object
  1918. */
  1919. public long getLength() {
  1920. if (length == -1)
  1921. return length = file.length();
  1922. return length;
  1923. }
  1924. /**
  1925. * @return the filename
  1926. */
  1927. public String getName() {
  1928. return file.getName();
  1929. }
  1930. /**
  1931. * @return the file the attributes apply to
  1932. */
  1933. public File getFile() {
  1934. return file;
  1935. }
  1936. boolean exists() {
  1937. return exists;
  1938. }
  1939. }
  1940. /**
  1941. * Get the file attributes we care for.
  1942. *
  1943. * @param path
  1944. * a {@link java.io.File} object.
  1945. * @return the file attributes we care for.
  1946. * @since 3.3
  1947. */
  1948. public Attributes getAttributes(File path) {
  1949. boolean isDirectory = isDirectory(path);
  1950. boolean isFile = !isDirectory && path.isFile();
  1951. assert path.exists() == isDirectory || isFile;
  1952. boolean exists = isDirectory || isFile;
  1953. boolean canExecute = exists && !isDirectory && canExecute(path);
  1954. boolean isSymlink = false;
  1955. Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
  1956. long createTime = 0L;
  1957. return new Attributes(this, path, exists, isDirectory, canExecute,
  1958. isSymlink, isFile, createTime, lastModified, -1);
  1959. }
  1960. /**
  1961. * Normalize the unicode path to composed form.
  1962. *
  1963. * @param file
  1964. * a {@link java.io.File} object.
  1965. * @return NFC-format File
  1966. * @since 3.3
  1967. */
  1968. public File normalize(File file) {
  1969. return file;
  1970. }
  1971. /**
  1972. * Normalize the unicode path to composed form.
  1973. *
  1974. * @param name
  1975. * path name
  1976. * @return NFC-format string
  1977. * @since 3.3
  1978. */
  1979. public String normalize(String name) {
  1980. return name;
  1981. }
  1982. /**
  1983. * This runnable will consume an input stream's content into an output
  1984. * stream as soon as it gets available.
  1985. * <p>
  1986. * Typically used to empty processes' standard output and error, preventing
  1987. * them to choke.
  1988. * </p>
  1989. * <p>
  1990. * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  1991. * streams.
  1992. * </p>
  1993. */
  1994. private static class StreamGobbler implements Runnable {
  1995. private InputStream in;
  1996. private OutputStream out;
  1997. public StreamGobbler(InputStream stream, OutputStream output) {
  1998. this.in = stream;
  1999. this.out = output;
  2000. }
  2001. @Override
  2002. public void run() {
  2003. try {
  2004. copy();
  2005. } catch (IOException e) {
  2006. // Do nothing on read failure; leave streams open.
  2007. }
  2008. }
  2009. void copy() throws IOException {
  2010. boolean writeFailure = false;
  2011. byte buffer[] = new byte[4096];
  2012. int readBytes;
  2013. while ((readBytes = in.read(buffer)) != -1) {
  2014. // Do not try to write again after a failure, but keep
  2015. // reading as long as possible to prevent the input stream
  2016. // from choking.
  2017. if (!writeFailure && out != null) {
  2018. try {
  2019. out.write(buffer, 0, readBytes);
  2020. out.flush();
  2021. } catch (IOException e) {
  2022. writeFailure = true;
  2023. }
  2024. }
  2025. }
  2026. }
  2027. }
  2028. }