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