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