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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196
  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.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
  966. .doPrivileged(new PrivilegedAction<String>() {
  967. @Override
  968. public String run() {
  969. return System.getProperty("user.home"); //$NON-NLS-1$
  970. }
  971. });
  972. if (home == null || home.length() == 0)
  973. return null;
  974. return new File(home).getAbsoluteFile();
  975. }
  976. /**
  977. * Searches the given path to see if it contains one of the given files.
  978. * Returns the first it finds. Returns null if not found or if path is null.
  979. *
  980. * @param path
  981. * List of paths to search separated by File.pathSeparator
  982. * @param lookFor
  983. * Files to search for in the given path
  984. * @return the first match found, or null
  985. * @since 3.0
  986. */
  987. protected static File searchPath(String path, String... lookFor) {
  988. if (path == null)
  989. return null;
  990. for (String p : path.split(File.pathSeparator)) {
  991. for (String command : lookFor) {
  992. final File e = new File(p, command);
  993. if (e.isFile())
  994. return e.getAbsoluteFile();
  995. }
  996. }
  997. return null;
  998. }
  999. /**
  1000. * Execute a command and return a single line of output as a String
  1001. *
  1002. * @param dir
  1003. * Working directory for the command
  1004. * @param command
  1005. * as component array
  1006. * @param encoding
  1007. * to be used to parse the command's output
  1008. * @return the one-line output of the command or {@code null} if there is
  1009. * none
  1010. * @throws org.eclipse.jgit.errors.CommandFailedException
  1011. * thrown when the command failed (return code was non-zero)
  1012. */
  1013. @Nullable
  1014. protected static String readPipe(File dir, String[] command,
  1015. String encoding) throws CommandFailedException {
  1016. return readPipe(dir, command, encoding, null);
  1017. }
  1018. /**
  1019. * Execute a command and return a single line of output as a String
  1020. *
  1021. * @param dir
  1022. * Working directory for the command
  1023. * @param command
  1024. * as component array
  1025. * @param encoding
  1026. * to be used to parse the command's output
  1027. * @param env
  1028. * Map of environment variables to be merged with those of the
  1029. * current process
  1030. * @return the one-line output of the command or {@code null} if there is
  1031. * none
  1032. * @throws org.eclipse.jgit.errors.CommandFailedException
  1033. * thrown when the command failed (return code was non-zero)
  1034. * @since 4.0
  1035. */
  1036. @Nullable
  1037. protected static String readPipe(File dir, String[] command,
  1038. String encoding, Map<String, String> env)
  1039. throws CommandFailedException {
  1040. final boolean debug = LOG.isDebugEnabled();
  1041. try {
  1042. if (debug) {
  1043. LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  1044. + dir);
  1045. }
  1046. ProcessBuilder pb = new ProcessBuilder(command);
  1047. pb.directory(dir);
  1048. if (env != null) {
  1049. pb.environment().putAll(env);
  1050. }
  1051. Process p;
  1052. try {
  1053. p = pb.start();
  1054. } catch (IOException e) {
  1055. // Process failed to start
  1056. throw new CommandFailedException(-1, e.getMessage(), e);
  1057. }
  1058. p.getOutputStream().close();
  1059. GobblerThread gobbler = new GobblerThread(p, command, dir);
  1060. gobbler.start();
  1061. String r = null;
  1062. try (BufferedReader lineRead = new BufferedReader(
  1063. new InputStreamReader(p.getInputStream(), encoding))) {
  1064. r = lineRead.readLine();
  1065. if (debug) {
  1066. LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  1067. LOG.debug("remaining output:\n"); //$NON-NLS-1$
  1068. String l;
  1069. while ((l = lineRead.readLine()) != null) {
  1070. LOG.debug(l);
  1071. }
  1072. }
  1073. }
  1074. for (;;) {
  1075. try {
  1076. int rc = p.waitFor();
  1077. gobbler.join();
  1078. if (rc == 0 && !gobbler.fail.get()) {
  1079. return r;
  1080. } else {
  1081. if (debug) {
  1082. LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
  1083. }
  1084. throw new CommandFailedException(rc,
  1085. gobbler.errorMessage.get(),
  1086. gobbler.exception.get());
  1087. }
  1088. } catch (InterruptedException ie) {
  1089. // Stop bothering me, I have a zombie to reap.
  1090. }
  1091. }
  1092. } catch (IOException e) {
  1093. LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
  1094. }
  1095. if (debug) {
  1096. LOG.debug("readpipe returns null"); //$NON-NLS-1$
  1097. }
  1098. return null;
  1099. }
  1100. private static class GobblerThread extends Thread {
  1101. /* The process has 5 seconds to exit after closing stderr */
  1102. private static final int PROCESS_EXIT_TIMEOUT = 5;
  1103. private final Process p;
  1104. private final String desc;
  1105. private final String dir;
  1106. final AtomicBoolean fail = new AtomicBoolean();
  1107. final AtomicReference<String> errorMessage = new AtomicReference<>();
  1108. final AtomicReference<Throwable> exception = new AtomicReference<>();
  1109. GobblerThread(Process p, String[] command, File dir) {
  1110. this.p = p;
  1111. this.desc = Arrays.toString(command);
  1112. this.dir = Objects.toString(dir);
  1113. }
  1114. @Override
  1115. public void run() {
  1116. StringBuilder err = new StringBuilder();
  1117. try (InputStream is = p.getErrorStream()) {
  1118. int ch;
  1119. while ((ch = is.read()) != -1) {
  1120. err.append((char) ch);
  1121. }
  1122. } catch (IOException e) {
  1123. if (waitForProcessCompletion(e) && p.exitValue() != 0) {
  1124. setError(e, e.getMessage(), p.exitValue());
  1125. fail.set(true);
  1126. } else {
  1127. // ignore. command terminated faster and stream was just closed
  1128. // or the process didn't terminate within timeout
  1129. }
  1130. } finally {
  1131. if (waitForProcessCompletion(null) && err.length() > 0) {
  1132. setError(null, err.toString(), p.exitValue());
  1133. if (p.exitValue() != 0) {
  1134. fail.set(true);
  1135. }
  1136. }
  1137. }
  1138. }
  1139. @SuppressWarnings("boxing")
  1140. private boolean waitForProcessCompletion(IOException originalError) {
  1141. try {
  1142. if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
  1143. setError(originalError, MessageFormat.format(
  1144. JGitText.get().commandClosedStderrButDidntExit,
  1145. desc, PROCESS_EXIT_TIMEOUT), -1);
  1146. fail.set(true);
  1147. return false;
  1148. }
  1149. } catch (InterruptedException e) {
  1150. setError(originalError, MessageFormat.format(
  1151. JGitText.get().threadInterruptedWhileRunning, desc), -1);
  1152. fail.set(true);
  1153. return false;
  1154. }
  1155. return true;
  1156. }
  1157. private void setError(IOException e, String message, int exitCode) {
  1158. exception.set(e);
  1159. errorMessage.set(MessageFormat.format(
  1160. JGitText.get().exceptionCaughtDuringExecutionOfCommand,
  1161. desc, dir, Integer.valueOf(exitCode), message));
  1162. }
  1163. }
  1164. /**
  1165. * Discover the path to the Git executable.
  1166. *
  1167. * @return the path to the Git executable or {@code null} if it cannot be
  1168. * determined.
  1169. * @since 4.0
  1170. */
  1171. protected abstract File discoverGitExe();
  1172. /**
  1173. * Discover the path to the system-wide Git configuration file
  1174. *
  1175. * @return the path to the system-wide Git configuration file or
  1176. * {@code null} if it cannot be determined.
  1177. * @since 4.0
  1178. */
  1179. protected File discoverGitSystemConfig() {
  1180. File gitExe = discoverGitExe();
  1181. if (gitExe == null) {
  1182. return null;
  1183. }
  1184. // Bug 480782: Check if the discovered git executable is JGit CLI
  1185. String v;
  1186. try {
  1187. v = readPipe(gitExe.getParentFile(),
  1188. new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
  1189. Charset.defaultCharset().name());
  1190. } catch (CommandFailedException e) {
  1191. LOG.warn(e.getMessage());
  1192. return null;
  1193. }
  1194. if (StringUtils.isEmptyOrNull(v)
  1195. || (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
  1196. return null;
  1197. }
  1198. // Trick Git into printing the path to the config file by using "echo"
  1199. // as the editor.
  1200. Map<String, String> env = new HashMap<>();
  1201. env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
  1202. String w;
  1203. try {
  1204. w = readPipe(gitExe.getParentFile(),
  1205. new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  1206. Charset.defaultCharset().name(), env);
  1207. } catch (CommandFailedException e) {
  1208. LOG.warn(e.getMessage());
  1209. return null;
  1210. }
  1211. if (StringUtils.isEmptyOrNull(w)) {
  1212. return null;
  1213. }
  1214. return new File(w);
  1215. }
  1216. /**
  1217. * Get the currently used path to the system-wide Git configuration file.
  1218. *
  1219. * @return the currently used path to the system-wide Git configuration file
  1220. * or {@code null} if none has been set.
  1221. * @since 4.0
  1222. */
  1223. public File getGitSystemConfig() {
  1224. if (gitSystemConfig == null) {
  1225. gitSystemConfig = new Holder<>(discoverGitSystemConfig());
  1226. }
  1227. return gitSystemConfig.value;
  1228. }
  1229. /**
  1230. * Set the path to the system-wide Git configuration file to use.
  1231. *
  1232. * @param configFile
  1233. * the path to the config file.
  1234. * @return {@code this}
  1235. * @since 4.0
  1236. */
  1237. public FS setGitSystemConfig(File configFile) {
  1238. gitSystemConfig = new Holder<>(configFile);
  1239. return this;
  1240. }
  1241. /**
  1242. * Get the parent directory of this file's parent directory
  1243. *
  1244. * @param grandchild
  1245. * a {@link java.io.File} object.
  1246. * @return the parent directory of this file's parent directory or
  1247. * {@code null} in case there's no grandparent directory
  1248. * @since 4.0
  1249. */
  1250. protected static File resolveGrandparentFile(File grandchild) {
  1251. if (grandchild != null) {
  1252. File parent = grandchild.getParentFile();
  1253. if (parent != null)
  1254. return parent.getParentFile();
  1255. }
  1256. return null;
  1257. }
  1258. /**
  1259. * Check if a file is a symbolic link and read it
  1260. *
  1261. * @param path
  1262. * a {@link java.io.File} object.
  1263. * @return target of link or null
  1264. * @throws java.io.IOException
  1265. * @since 3.0
  1266. */
  1267. public String readSymLink(File path) throws IOException {
  1268. return FileUtils.readSymLink(path);
  1269. }
  1270. /**
  1271. * Whether the path is a symbolic link (and we support these).
  1272. *
  1273. * @param path
  1274. * a {@link java.io.File} object.
  1275. * @return true if the path is a symbolic link (and we support these)
  1276. * @throws java.io.IOException
  1277. * @since 3.0
  1278. */
  1279. public boolean isSymLink(File path) throws IOException {
  1280. return FileUtils.isSymlink(path);
  1281. }
  1282. /**
  1283. * Tests if the path exists, in case of a symbolic link, true even if the
  1284. * target does not exist
  1285. *
  1286. * @param path
  1287. * a {@link java.io.File} object.
  1288. * @return true if path exists
  1289. * @since 3.0
  1290. */
  1291. public boolean exists(File path) {
  1292. return FileUtils.exists(path);
  1293. }
  1294. /**
  1295. * Check if path is a directory. If the OS/JRE supports symbolic links and
  1296. * path is a symbolic link to a directory, this method returns false.
  1297. *
  1298. * @param path
  1299. * a {@link java.io.File} object.
  1300. * @return true if file is a directory,
  1301. * @since 3.0
  1302. */
  1303. public boolean isDirectory(File path) {
  1304. return FileUtils.isDirectory(path);
  1305. }
  1306. /**
  1307. * Examine if path represents a regular file. If the OS/JRE supports
  1308. * symbolic links the test returns false if path represents a symbolic link.
  1309. *
  1310. * @param path
  1311. * a {@link java.io.File} object.
  1312. * @return true if path represents a regular file
  1313. * @since 3.0
  1314. */
  1315. public boolean isFile(File path) {
  1316. return FileUtils.isFile(path);
  1317. }
  1318. /**
  1319. * Whether path is hidden, either starts with . on unix or has the hidden
  1320. * attribute in windows
  1321. *
  1322. * @param path
  1323. * a {@link java.io.File} object.
  1324. * @return true if path is hidden, either starts with . on unix or has the
  1325. * hidden attribute in windows
  1326. * @throws java.io.IOException
  1327. * @since 3.0
  1328. */
  1329. public boolean isHidden(File path) throws IOException {
  1330. return FileUtils.isHidden(path);
  1331. }
  1332. /**
  1333. * Set the hidden attribute for file whose name starts with a period.
  1334. *
  1335. * @param path
  1336. * a {@link java.io.File} object.
  1337. * @param hidden
  1338. * whether to set the file hidden
  1339. * @throws java.io.IOException
  1340. * @since 3.0
  1341. */
  1342. public void setHidden(File path, boolean hidden) throws IOException {
  1343. FileUtils.setHidden(path, hidden);
  1344. }
  1345. /**
  1346. * Create a symbolic link
  1347. *
  1348. * @param path
  1349. * a {@link java.io.File} object.
  1350. * @param target
  1351. * target path of the symlink
  1352. * @throws java.io.IOException
  1353. * @since 3.0
  1354. */
  1355. public void createSymLink(File path, String target) throws IOException {
  1356. FileUtils.createSymLink(path, target);
  1357. }
  1358. /**
  1359. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1360. * of this class may take care to provide a safe implementation for this
  1361. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1362. *
  1363. * @param path
  1364. * the file to be created
  1365. * @return <code>true</code> if the file was created, <code>false</code> if
  1366. * the file already existed
  1367. * @throws java.io.IOException
  1368. * @deprecated use {@link #createNewFileAtomic(File)} instead
  1369. * @since 4.5
  1370. */
  1371. @Deprecated
  1372. public boolean createNewFile(File path) throws IOException {
  1373. return path.createNewFile();
  1374. }
  1375. /**
  1376. * A token representing a file created by
  1377. * {@link #createNewFileAtomic(File)}. The token must be retained until the
  1378. * file has been deleted in order to guarantee that the unique file was
  1379. * created atomically. As soon as the file is no longer needed the lock
  1380. * token must be closed.
  1381. *
  1382. * @since 4.7
  1383. */
  1384. public static class LockToken implements Closeable {
  1385. private boolean isCreated;
  1386. private Optional<Path> link;
  1387. LockToken(boolean isCreated, Optional<Path> link) {
  1388. this.isCreated = isCreated;
  1389. this.link = link;
  1390. }
  1391. /**
  1392. * @return {@code true} if the file was created successfully
  1393. */
  1394. public boolean isCreated() {
  1395. return isCreated;
  1396. }
  1397. @Override
  1398. public void close() {
  1399. if (!link.isPresent()) {
  1400. return;
  1401. }
  1402. Path p = link.get();
  1403. if (!Files.exists(p)) {
  1404. return;
  1405. }
  1406. try {
  1407. Files.delete(p);
  1408. } catch (IOException e) {
  1409. LOG.error(MessageFormat
  1410. .format(JGitText.get().closeLockTokenFailed, this), e);
  1411. }
  1412. }
  1413. @Override
  1414. public String toString() {
  1415. return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
  1416. ", link=" //$NON-NLS-1$
  1417. + (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
  1418. : "<null>]"); //$NON-NLS-1$
  1419. }
  1420. }
  1421. /**
  1422. * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
  1423. * of this class may take care to provide a safe implementation for this
  1424. * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
  1425. *
  1426. * @param path
  1427. * the file to be created
  1428. * @return LockToken this token must be closed after the created file was
  1429. * deleted
  1430. * @throws IOException
  1431. * @since 4.7
  1432. */
  1433. public LockToken createNewFileAtomic(File path) throws IOException {
  1434. return new LockToken(path.createNewFile(), Optional.empty());
  1435. }
  1436. /**
  1437. * See
  1438. * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
  1439. *
  1440. * @param base
  1441. * The path against which <code>other</code> should be
  1442. * relativized.
  1443. * @param other
  1444. * The path that will be made relative to <code>base</code>.
  1445. * @return A relative path that, when resolved against <code>base</code>,
  1446. * will yield the original <code>other</code>.
  1447. * @see FileUtils#relativizePath(String, String, String, boolean)
  1448. * @since 3.7
  1449. */
  1450. public String relativize(String base, String other) {
  1451. return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
  1452. }
  1453. /**
  1454. * Enumerates children of a directory.
  1455. *
  1456. * @param directory
  1457. * to get the children of
  1458. * @param fileModeStrategy
  1459. * to use to calculate the git mode of a child
  1460. * @return an array of entries for the children
  1461. *
  1462. * @since 5.0
  1463. */
  1464. public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
  1465. final File[] all = directory.listFiles();
  1466. if (all == null) {
  1467. return NO_ENTRIES;
  1468. }
  1469. final Entry[] result = new Entry[all.length];
  1470. for (int i = 0; i < result.length; i++) {
  1471. result[i] = new FileEntry(all[i], this, fileModeStrategy);
  1472. }
  1473. return result;
  1474. }
  1475. /**
  1476. * Checks whether the given hook is defined for the given repository, then
  1477. * runs it with the given arguments.
  1478. * <p>
  1479. * The hook's standard output and error streams will be redirected to
  1480. * <code>System.out</code> and <code>System.err</code> respectively. The
  1481. * hook will have no stdin.
  1482. * </p>
  1483. *
  1484. * @param repository
  1485. * The repository for which a hook should be run.
  1486. * @param hookName
  1487. * The name of the hook to be executed.
  1488. * @param args
  1489. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1490. * but can be an empty array.
  1491. * @return The ProcessResult describing this hook's execution.
  1492. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1493. * if we fail to run the hook somehow. Causes may include an
  1494. * interrupted process or I/O errors.
  1495. * @since 4.0
  1496. */
  1497. public ProcessResult runHookIfPresent(Repository repository,
  1498. final String hookName,
  1499. String[] args) throws JGitInternalException {
  1500. return runHookIfPresent(repository, hookName, args, System.out, System.err,
  1501. null);
  1502. }
  1503. /**
  1504. * Checks whether the given hook is defined for the given repository, then
  1505. * runs it with the given arguments.
  1506. *
  1507. * @param repository
  1508. * The repository for which a hook should be run.
  1509. * @param hookName
  1510. * The name of the hook to be executed.
  1511. * @param args
  1512. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1513. * but can be an empty array.
  1514. * @param outRedirect
  1515. * A print stream on which to redirect the hook's stdout. Can be
  1516. * <code>null</code>, in which case the hook's standard output
  1517. * will be lost.
  1518. * @param errRedirect
  1519. * A print stream on which to redirect the hook's stderr. Can be
  1520. * <code>null</code>, in which case the hook's standard error
  1521. * will be lost.
  1522. * @param stdinArgs
  1523. * A string to pass on to the standard input of the hook. May be
  1524. * <code>null</code>.
  1525. * @return The ProcessResult describing this hook's execution.
  1526. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1527. * if we fail to run the hook somehow. Causes may include an
  1528. * interrupted process or I/O errors.
  1529. * @since 4.0
  1530. */
  1531. public ProcessResult runHookIfPresent(Repository repository,
  1532. final String hookName,
  1533. String[] args, PrintStream outRedirect, PrintStream errRedirect,
  1534. String stdinArgs) throws JGitInternalException {
  1535. return new ProcessResult(Status.NOT_SUPPORTED);
  1536. }
  1537. /**
  1538. * See
  1539. * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
  1540. * . Should only be called by FS supporting shell scripts execution.
  1541. *
  1542. * @param repository
  1543. * The repository for which a hook should be run.
  1544. * @param hookName
  1545. * The name of the hook to be executed.
  1546. * @param args
  1547. * Arguments to pass to this hook. Cannot be <code>null</code>,
  1548. * but can be an empty array.
  1549. * @param outRedirect
  1550. * A print stream on which to redirect the hook's stdout. Can be
  1551. * <code>null</code>, in which case the hook's standard output
  1552. * will be lost.
  1553. * @param errRedirect
  1554. * A print stream on which to redirect the hook's stderr. Can be
  1555. * <code>null</code>, in which case the hook's standard error
  1556. * will be lost.
  1557. * @param stdinArgs
  1558. * A string to pass on to the standard input of the hook. May be
  1559. * <code>null</code>.
  1560. * @return The ProcessResult describing this hook's execution.
  1561. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  1562. * if we fail to run the hook somehow. Causes may include an
  1563. * interrupted process or I/O errors.
  1564. * @since 4.0
  1565. */
  1566. protected ProcessResult internalRunHookIfPresent(Repository repository,
  1567. final String hookName, String[] args, PrintStream outRedirect,
  1568. PrintStream errRedirect, String stdinArgs)
  1569. throws JGitInternalException {
  1570. final File hookFile = findHook(repository, hookName);
  1571. if (hookFile == null)
  1572. return new ProcessResult(Status.NOT_PRESENT);
  1573. final String hookPath = hookFile.getAbsolutePath();
  1574. final File runDirectory;
  1575. if (repository.isBare())
  1576. runDirectory = repository.getDirectory();
  1577. else
  1578. runDirectory = repository.getWorkTree();
  1579. final String cmd = relativize(runDirectory.getAbsolutePath(),
  1580. hookPath);
  1581. ProcessBuilder hookProcess = runInShell(cmd, args);
  1582. hookProcess.directory(runDirectory);
  1583. Map<String, String> environment = hookProcess.environment();
  1584. environment.put(Constants.GIT_DIR_KEY,
  1585. repository.getDirectory().getAbsolutePath());
  1586. if (!repository.isBare()) {
  1587. environment.put(Constants.GIT_WORK_TREE_KEY,
  1588. repository.getWorkTree().getAbsolutePath());
  1589. }
  1590. try {
  1591. return new ProcessResult(runProcess(hookProcess, outRedirect,
  1592. errRedirect, stdinArgs), Status.OK);
  1593. } catch (IOException e) {
  1594. throw new JGitInternalException(MessageFormat.format(
  1595. JGitText.get().exceptionCaughtDuringExecutionOfHook,
  1596. hookName), e);
  1597. } catch (InterruptedException e) {
  1598. throw new JGitInternalException(MessageFormat.format(
  1599. JGitText.get().exceptionHookExecutionInterrupted,
  1600. hookName), e);
  1601. }
  1602. }
  1603. /**
  1604. * Tries to find a hook matching the given one in the given repository.
  1605. *
  1606. * @param repository
  1607. * The repository within which to find a hook.
  1608. * @param hookName
  1609. * The name of the hook we're trying to find.
  1610. * @return The {@link java.io.File} containing this particular hook if it
  1611. * exists in the given repository, <code>null</code> otherwise.
  1612. * @since 4.0
  1613. */
  1614. public File findHook(Repository repository, String hookName) {
  1615. File gitDir = repository.getDirectory();
  1616. if (gitDir == null)
  1617. return null;
  1618. final File hookFile = new File(new File(gitDir,
  1619. Constants.HOOKS), hookName);
  1620. return hookFile.isFile() ? hookFile : null;
  1621. }
  1622. /**
  1623. * Runs the given process until termination, clearing its stdout and stderr
  1624. * streams on-the-fly.
  1625. *
  1626. * @param processBuilder
  1627. * The process builder configured for this process.
  1628. * @param outRedirect
  1629. * A OutputStream on which to redirect the processes stdout. Can
  1630. * be <code>null</code>, in which case the processes standard
  1631. * output will be lost.
  1632. * @param errRedirect
  1633. * A OutputStream on which to redirect the processes stderr. Can
  1634. * be <code>null</code>, in which case the processes standard
  1635. * error will be lost.
  1636. * @param stdinArgs
  1637. * A string to pass on to the standard input of the hook. Can be
  1638. * <code>null</code>.
  1639. * @return the exit value of this process.
  1640. * @throws java.io.IOException
  1641. * if an I/O error occurs while executing this process.
  1642. * @throws java.lang.InterruptedException
  1643. * if the current thread is interrupted while waiting for the
  1644. * process to end.
  1645. * @since 4.2
  1646. */
  1647. public int runProcess(ProcessBuilder processBuilder,
  1648. OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
  1649. throws IOException, InterruptedException {
  1650. InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
  1651. stdinArgs.getBytes(UTF_8));
  1652. return runProcess(processBuilder, outRedirect, errRedirect, in);
  1653. }
  1654. /**
  1655. * Runs the given process until termination, clearing its stdout and stderr
  1656. * streams on-the-fly.
  1657. *
  1658. * @param processBuilder
  1659. * The process builder configured for this process.
  1660. * @param outRedirect
  1661. * An OutputStream on which to redirect the processes stdout. Can
  1662. * be <code>null</code>, in which case the processes standard
  1663. * output will be lost.
  1664. * @param errRedirect
  1665. * An OutputStream on which to redirect the processes stderr. Can
  1666. * be <code>null</code>, in which case the processes standard
  1667. * error will be lost.
  1668. * @param inRedirect
  1669. * An InputStream from which to redirect the processes stdin. Can
  1670. * be <code>null</code>, in which case the process doesn't get
  1671. * any data over stdin. It is assumed that the whole InputStream
  1672. * will be consumed by the process. The method will close the
  1673. * inputstream after all bytes are read.
  1674. * @return the return code of this process.
  1675. * @throws java.io.IOException
  1676. * if an I/O error occurs while executing this process.
  1677. * @throws java.lang.InterruptedException
  1678. * if the current thread is interrupted while waiting for the
  1679. * process to end.
  1680. * @since 4.2
  1681. */
  1682. public int runProcess(ProcessBuilder processBuilder,
  1683. OutputStream outRedirect, OutputStream errRedirect,
  1684. InputStream inRedirect) throws IOException,
  1685. InterruptedException {
  1686. final ExecutorService executor = Executors.newFixedThreadPool(2);
  1687. Process process = null;
  1688. // We'll record the first I/O exception that occurs, but keep on trying
  1689. // to dispose of our open streams and file handles
  1690. IOException ioException = null;
  1691. try {
  1692. process = processBuilder.start();
  1693. executor.execute(
  1694. new StreamGobbler(process.getErrorStream(), errRedirect));
  1695. executor.execute(
  1696. new StreamGobbler(process.getInputStream(), outRedirect));
  1697. @SuppressWarnings("resource") // Closed in the finally block
  1698. OutputStream outputStream = process.getOutputStream();
  1699. try {
  1700. if (inRedirect != null) {
  1701. new StreamGobbler(inRedirect, outputStream).copy();
  1702. }
  1703. } finally {
  1704. try {
  1705. outputStream.close();
  1706. } catch (IOException e) {
  1707. // When the process exits before consuming the input, the OutputStream
  1708. // is replaced with the null output stream. This null output stream
  1709. // throws IOException for all write calls. When StreamGobbler fails to
  1710. // flush the buffer because of this, this close call tries to flush it
  1711. // again. This causes another IOException. Since we ignore the
  1712. // IOException in StreamGobbler, we also ignore the exception here.
  1713. }
  1714. }
  1715. return process.waitFor();
  1716. } catch (IOException e) {
  1717. ioException = e;
  1718. } finally {
  1719. shutdownAndAwaitTermination(executor);
  1720. if (process != null) {
  1721. try {
  1722. process.waitFor();
  1723. } catch (InterruptedException e) {
  1724. // Thrown by the outer try.
  1725. // Swallow this one to carry on our cleanup, and clear the
  1726. // interrupted flag (processes throw the exception without
  1727. // clearing the flag).
  1728. Thread.interrupted();
  1729. }
  1730. // A process doesn't clean its own resources even when destroyed
  1731. // Explicitly try and close all three streams, preserving the
  1732. // outer I/O exception if any.
  1733. if (inRedirect != null) {
  1734. inRedirect.close();
  1735. }
  1736. try {
  1737. process.getErrorStream().close();
  1738. } catch (IOException e) {
  1739. ioException = ioException != null ? ioException : e;
  1740. }
  1741. try {
  1742. process.getInputStream().close();
  1743. } catch (IOException e) {
  1744. ioException = ioException != null ? ioException : e;
  1745. }
  1746. try {
  1747. process.getOutputStream().close();
  1748. } catch (IOException e) {
  1749. ioException = ioException != null ? ioException : e;
  1750. }
  1751. process.destroy();
  1752. }
  1753. }
  1754. // We can only be here if the outer try threw an IOException.
  1755. throw ioException;
  1756. }
  1757. /**
  1758. * Shuts down an {@link ExecutorService} in two phases, first by calling
  1759. * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
  1760. * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
  1761. * necessary, to cancel any lingering tasks. Returns true if the pool has
  1762. * been properly shutdown, false otherwise.
  1763. * <p>
  1764. *
  1765. * @param pool
  1766. * the pool to shutdown
  1767. * @return <code>true</code> if the pool has been properly shutdown,
  1768. * <code>false</code> otherwise.
  1769. */
  1770. private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
  1771. boolean hasShutdown = true;
  1772. pool.shutdown(); // Disable new tasks from being submitted
  1773. try {
  1774. // Wait a while for existing tasks to terminate
  1775. if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
  1776. pool.shutdownNow(); // Cancel currently executing tasks
  1777. // Wait a while for tasks to respond to being canceled
  1778. if (!pool.awaitTermination(60, TimeUnit.SECONDS))
  1779. hasShutdown = false;
  1780. }
  1781. } catch (InterruptedException ie) {
  1782. // (Re-)Cancel if current thread also interrupted
  1783. pool.shutdownNow();
  1784. // Preserve interrupt status
  1785. Thread.currentThread().interrupt();
  1786. hasShutdown = false;
  1787. }
  1788. return hasShutdown;
  1789. }
  1790. /**
  1791. * Initialize a ProcessBuilder to run a command using the system shell.
  1792. *
  1793. * @param cmd
  1794. * command to execute. This string should originate from the
  1795. * end-user, and thus is platform specific.
  1796. * @param args
  1797. * arguments to pass to command. These should be protected from
  1798. * shell evaluation.
  1799. * @return a partially completed process builder. Caller should finish
  1800. * populating directory, environment, and then start the process.
  1801. */
  1802. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  1803. /**
  1804. * Execute a command defined by a {@link java.lang.ProcessBuilder}.
  1805. *
  1806. * @param pb
  1807. * The command to be executed
  1808. * @param in
  1809. * The standard input stream passed to the process
  1810. * @return The result of the executed command
  1811. * @throws java.lang.InterruptedException
  1812. * @throws java.io.IOException
  1813. * @since 4.2
  1814. */
  1815. public ExecutionResult execute(ProcessBuilder pb, InputStream in)
  1816. throws IOException, InterruptedException {
  1817. try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
  1818. TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
  1819. 1024 * 1024)) {
  1820. int rc = runProcess(pb, stdout, stderr, in);
  1821. return new ExecutionResult(stdout, stderr, rc);
  1822. }
  1823. }
  1824. private static class Holder<V> {
  1825. final V value;
  1826. Holder(V value) {
  1827. this.value = value;
  1828. }
  1829. }
  1830. /**
  1831. * File attributes we typically care for.
  1832. *
  1833. * @since 3.3
  1834. */
  1835. public static class Attributes {
  1836. /**
  1837. * @return true if this are the attributes of a directory
  1838. */
  1839. public boolean isDirectory() {
  1840. return isDirectory;
  1841. }
  1842. /**
  1843. * @return true if this are the attributes of an executable file
  1844. */
  1845. public boolean isExecutable() {
  1846. return isExecutable;
  1847. }
  1848. /**
  1849. * @return true if this are the attributes of a symbolic link
  1850. */
  1851. public boolean isSymbolicLink() {
  1852. return isSymbolicLink;
  1853. }
  1854. /**
  1855. * @return true if this are the attributes of a regular file
  1856. */
  1857. public boolean isRegularFile() {
  1858. return isRegularFile;
  1859. }
  1860. /**
  1861. * @return the time when the file was created
  1862. */
  1863. public long getCreationTime() {
  1864. return creationTime;
  1865. }
  1866. /**
  1867. * @return the time (milliseconds since 1970-01-01) when this object was
  1868. * last modified
  1869. * @deprecated use getLastModifiedInstant instead
  1870. */
  1871. @Deprecated
  1872. public long getLastModifiedTime() {
  1873. return lastModifiedInstant.toEpochMilli();
  1874. }
  1875. /**
  1876. * @return the time when this object was last modified
  1877. * @since 5.1.9
  1878. */
  1879. public Instant getLastModifiedInstant() {
  1880. return lastModifiedInstant;
  1881. }
  1882. private final boolean isDirectory;
  1883. private final boolean isSymbolicLink;
  1884. private final boolean isRegularFile;
  1885. private final long creationTime;
  1886. private final Instant lastModifiedInstant;
  1887. private final boolean isExecutable;
  1888. private final File file;
  1889. private final boolean exists;
  1890. /**
  1891. * file length
  1892. */
  1893. protected long length = -1;
  1894. final FS fs;
  1895. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  1896. boolean isExecutable, boolean isSymbolicLink,
  1897. boolean isRegularFile, long creationTime,
  1898. Instant lastModifiedInstant, long length) {
  1899. this.fs = fs;
  1900. this.file = file;
  1901. this.exists = exists;
  1902. this.isDirectory = isDirectory;
  1903. this.isExecutable = isExecutable;
  1904. this.isSymbolicLink = isSymbolicLink;
  1905. this.isRegularFile = isRegularFile;
  1906. this.creationTime = creationTime;
  1907. this.lastModifiedInstant = lastModifiedInstant;
  1908. this.length = length;
  1909. }
  1910. /**
  1911. * Constructor when there are issues with reading. All attributes except
  1912. * given will be set to the default values.
  1913. *
  1914. * @param fs
  1915. * @param path
  1916. */
  1917. public Attributes(File path, FS fs) {
  1918. this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
  1919. }
  1920. /**
  1921. * @return length of this file object
  1922. */
  1923. public long getLength() {
  1924. if (length == -1)
  1925. return length = file.length();
  1926. return length;
  1927. }
  1928. /**
  1929. * @return the filename
  1930. */
  1931. public String getName() {
  1932. return file.getName();
  1933. }
  1934. /**
  1935. * @return the file the attributes apply to
  1936. */
  1937. public File getFile() {
  1938. return file;
  1939. }
  1940. boolean exists() {
  1941. return exists;
  1942. }
  1943. }
  1944. /**
  1945. * Get the file attributes we care for.
  1946. *
  1947. * @param path
  1948. * a {@link java.io.File} object.
  1949. * @return the file attributes we care for.
  1950. * @since 3.3
  1951. */
  1952. public Attributes getAttributes(File path) {
  1953. boolean isDirectory = isDirectory(path);
  1954. boolean isFile = !isDirectory && path.isFile();
  1955. assert path.exists() == isDirectory || isFile;
  1956. boolean exists = isDirectory || isFile;
  1957. boolean canExecute = exists && !isDirectory && canExecute(path);
  1958. boolean isSymlink = false;
  1959. Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
  1960. long createTime = 0L;
  1961. return new Attributes(this, path, exists, isDirectory, canExecute,
  1962. isSymlink, isFile, createTime, lastModified, -1);
  1963. }
  1964. /**
  1965. * Normalize the unicode path to composed form.
  1966. *
  1967. * @param file
  1968. * a {@link java.io.File} object.
  1969. * @return NFC-format File
  1970. * @since 3.3
  1971. */
  1972. public File normalize(File file) {
  1973. return file;
  1974. }
  1975. /**
  1976. * Normalize the unicode path to composed form.
  1977. *
  1978. * @param name
  1979. * path name
  1980. * @return NFC-format string
  1981. * @since 3.3
  1982. */
  1983. public String normalize(String name) {
  1984. return name;
  1985. }
  1986. /**
  1987. * This runnable will consume an input stream's content into an output
  1988. * stream as soon as it gets available.
  1989. * <p>
  1990. * Typically used to empty processes' standard output and error, preventing
  1991. * them to choke.
  1992. * </p>
  1993. * <p>
  1994. * <b>Note</b> that a {@link StreamGobbler} will never close either of its
  1995. * streams.
  1996. * </p>
  1997. */
  1998. private static class StreamGobbler implements Runnable {
  1999. private InputStream in;
  2000. private OutputStream out;
  2001. public StreamGobbler(InputStream stream, OutputStream output) {
  2002. this.in = stream;
  2003. this.out = output;
  2004. }
  2005. @Override
  2006. public void run() {
  2007. try {
  2008. copy();
  2009. } catch (IOException e) {
  2010. // Do nothing on read failure; leave streams open.
  2011. }
  2012. }
  2013. void copy() throws IOException {
  2014. boolean writeFailure = false;
  2015. byte buffer[] = new byte[4096];
  2016. int readBytes;
  2017. while ((readBytes = in.read(buffer)) != -1) {
  2018. // Do not try to write again after a failure, but keep
  2019. // reading as long as possible to prevent the input stream
  2020. // from choking.
  2021. if (!writeFailure && out != null) {
  2022. try {
  2023. out.write(buffer, 0, readBytes);
  2024. out.flush();
  2025. } catch (IOException e) {
  2026. writeFailure = true;
  2027. }
  2028. }
  2029. }
  2030. }
  2031. }
  2032. }