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