Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

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