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