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