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  
48  import java.io.BufferedReader;
49  import java.io.ByteArrayInputStream;
50  import java.io.Closeable;
51  import java.io.File;
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.InputStreamReader;
55  import java.io.OutputStream;
56  import java.io.PrintStream;
57  import java.nio.charset.Charset;
58  import java.nio.file.Files;
59  import java.nio.file.Path;
60  import java.nio.file.attribute.BasicFileAttributes;
61  import java.security.AccessController;
62  import java.security.PrivilegedAction;
63  import java.text.MessageFormat;
64  import java.util.Arrays;
65  import java.util.HashMap;
66  import java.util.Map;
67  import java.util.Objects;
68  import java.util.Optional;
69  import java.util.concurrent.ExecutorService;
70  import java.util.concurrent.Executors;
71  import java.util.concurrent.TimeUnit;
72  import java.util.concurrent.atomic.AtomicBoolean;
73  import java.util.concurrent.atomic.AtomicReference;
74  
75  import org.eclipse.jgit.annotations.Nullable;
76  import org.eclipse.jgit.api.errors.JGitInternalException;
77  import org.eclipse.jgit.errors.CommandFailedException;
78  import org.eclipse.jgit.internal.JGitText;
79  import org.eclipse.jgit.lib.Constants;
80  import org.eclipse.jgit.lib.Repository;
81  import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
82  import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
83  import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
84  import org.eclipse.jgit.util.ProcessResult.Status;
85  import org.slf4j.Logger;
86  import org.slf4j.LoggerFactory;
87  
88  /**
89   * Abstraction to support various file system operations not in Java.
90   */
91  public abstract class FS {
92  	private static final Logger LOG = LoggerFactory.getLogger(FS.class);
93  
94  	/**
95  	 * An empty array of entries, suitable as a return value for
96  	 * {@link #list(File, FileModeStrategy)}.
97  	 *
98  	 * @since 5.0
99  	 */
100 	protected static final Entry[] NO_ENTRIES = {};
101 
102 	/**
103 	 * This class creates FS instances. It will be overridden by a Java7 variant
104 	 * if such can be detected in {@link #detect(Boolean)}.
105 	 *
106 	 * @since 3.0
107 	 */
108 	public static class FSFactory {
109 		/**
110 		 * Constructor
111 		 */
112 		protected FSFactory() {
113 			// empty
114 		}
115 
116 		/**
117 		 * Detect the file system
118 		 *
119 		 * @param cygwinUsed
120 		 * @return FS instance
121 		 */
122 		public FS detect(Boolean cygwinUsed) {
123 			if (SystemReader.getInstance().isWindows()) {
124 				if (cygwinUsed == null)
125 					cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
126 				if (cygwinUsed.booleanValue())
127 					return new FS_Win32_Cygwin();
128 				else
129 					return new FS_Win32();
130 			} else {
131 				return new FS_POSIX();
132 			}
133 		}
134 	}
135 
136 	/**
137 	 * Result of an executed process. The caller is responsible to close the
138 	 * contained {@link TemporaryBuffer}s
139 	 *
140 	 * @since 4.2
141 	 */
142 	public static class ExecutionResult {
143 		private TemporaryBuffer stdout;
144 
145 		private TemporaryBuffer stderr;
146 
147 		private int rc;
148 
149 		/**
150 		 * @param stdout
151 		 * @param stderr
152 		 * @param rc
153 		 */
154 		public ExecutionResult(TemporaryBuffer./../org/eclipse/jgit/util/TemporaryBuffer.html#TemporaryBuffer">TemporaryBuffer stdout, TemporaryBuffer stderr,
155 				int rc) {
156 			this.stdout = stdout;
157 			this.stderr = stderr;
158 			this.rc = rc;
159 		}
160 
161 		/**
162 		 * @return buffered standard output stream
163 		 */
164 		public TemporaryBuffer getStdout() {
165 			return stdout;
166 		}
167 
168 		/**
169 		 * @return buffered standard error stream
170 		 */
171 		public TemporaryBuffer getStderr() {
172 			return stderr;
173 		}
174 
175 		/**
176 		 * @return the return code of the process
177 		 */
178 		public int getRc() {
179 			return rc;
180 		}
181 	}
182 
183 	/** The auto-detected implementation selected for this operating system and JRE. */
184 	public static final FS DETECTED = detect();
185 
186 	private volatile static FSFactory factory;
187 
188 	/**
189 	 * Auto-detect the appropriate file system abstraction.
190 	 *
191 	 * @return detected file system abstraction
192 	 */
193 	public static FS detect() {
194 		return detect(null);
195 	}
196 
197 	/**
198 	 * Auto-detect the appropriate file system abstraction, taking into account
199 	 * the presence of a Cygwin installation on the system. Using jgit in
200 	 * combination with Cygwin requires a more elaborate (and possibly slower)
201 	 * resolution of file system paths.
202 	 *
203 	 * @param cygwinUsed
204 	 *            <ul>
205 	 *            <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
206 	 *            combination with jgit</li>
207 	 *            <li><code>Boolean.FALSE</code> to assume that Cygwin is
208 	 *            <b>not</b> used with jgit</li>
209 	 *            <li><code>null</code> to auto-detect whether a Cygwin
210 	 *            installation is present on the system and in this case assume
211 	 *            that Cygwin is used</li>
212 	 *            </ul>
213 	 *
214 	 *            Note: this parameter is only relevant on Windows.
215 	 * @return detected file system abstraction
216 	 */
217 	public static FS detect(Boolean cygwinUsed) {
218 		if (factory == null) {
219 			factory = new FS.FSFactory();
220 		}
221 		return factory.detect(cygwinUsed);
222 	}
223 
224 	private volatile Holder<File> userHome;
225 
226 	private volatile Holder<File> gitSystemConfig;
227 
228 	/**
229 	 * Constructs a file system abstraction.
230 	 */
231 	protected FS() {
232 		// Do nothing by default.
233 	}
234 
235 	/**
236 	 * Initialize this FS using another's current settings.
237 	 *
238 	 * @param src
239 	 *            the source FS to copy from.
240 	 */
241 	protected FSme="FS" href="../../../../org/eclipse/jgit/util/FS.html#FS">FS(FS src) {
242 		userHome = src.userHome;
243 		gitSystemConfig = src.gitSystemConfig;
244 	}
245 
246 	/**
247 	 * Create a new instance of the same type of FS.
248 	 *
249 	 * @return a new instance of the same type of FS.
250 	 */
251 	public abstract FS newInstance();
252 
253 	/**
254 	 * Does this operating system and JRE support the execute flag on files?
255 	 *
256 	 * @return true if this implementation can provide reasonably accurate
257 	 *         executable bit information; false otherwise.
258 	 */
259 	public abstract boolean supportsExecute();
260 
261 	/**
262 	 * Does this file system support atomic file creation via
263 	 * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
264 	 * not guaranteed that when two file system clients run createNewFile() in
265 	 * parallel only one will succeed. In such cases both clients may think they
266 	 * created a new file.
267 	 *
268 	 * @return true if this implementation support atomic creation of new Files
269 	 *         by {@link java.io.File#createNewFile()}
270 	 * @since 4.5
271 	 */
272 	public boolean supportsAtomicCreateNewFile() {
273 		return true;
274 	}
275 
276 	/**
277 	 * Does this operating system and JRE supports symbolic links. The
278 	 * capability to handle symbolic links is detected at runtime.
279 	 *
280 	 * @return true if symbolic links may be used
281 	 * @since 3.0
282 	 */
283 	public boolean supportsSymlinks() {
284 		return false;
285 	}
286 
287 	/**
288 	 * Is this file system case sensitive
289 	 *
290 	 * @return true if this implementation is case sensitive
291 	 */
292 	public abstract boolean isCaseSensitive();
293 
294 	/**
295 	 * Determine if the file is executable (or not).
296 	 * <p>
297 	 * Not all platforms and JREs support executable flags on files. If the
298 	 * feature is unsupported this method will always return false.
299 	 * <p>
300 	 * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
301 	 * this method returns false, rather than the state of the executable flags
302 	 * on the target file.</em>
303 	 *
304 	 * @param f
305 	 *            abstract path to test.
306 	 * @return true if the file is believed to be executable by the user.
307 	 */
308 	public abstract boolean canExecute(File f);
309 
310 	/**
311 	 * Set a file to be executable by the user.
312 	 * <p>
313 	 * Not all platforms and JREs support executable flags on files. If the
314 	 * feature is unsupported this method will always return false and no
315 	 * changes will be made to the file specified.
316 	 *
317 	 * @param f
318 	 *            path to modify the executable status of.
319 	 * @param canExec
320 	 *            true to enable execution; false to disable it.
321 	 * @return true if the change succeeded; false otherwise.
322 	 */
323 	public abstract boolean setExecute(File f, boolean canExec);
324 
325 	/**
326 	 * Get the last modified time of a file system object. If the OS/JRE support
327 	 * symbolic links, the modification time of the link is returned, rather
328 	 * than that of the link target.
329 	 *
330 	 * @param f
331 	 *            a {@link java.io.File} object.
332 	 * @return last modified time of f
333 	 * @throws java.io.IOException
334 	 * @since 3.0
335 	 */
336 	public long lastModified(File f) throws IOException {
337 		return FileUtils.lastModified(f);
338 	}
339 
340 	/**
341 	 * Set the last modified time of a file system object. If the OS/JRE support
342 	 * symbolic links, the link is modified, not the target,
343 	 *
344 	 * @param f
345 	 *            a {@link java.io.File} object.
346 	 * @param time
347 	 *            last modified time
348 	 * @throws java.io.IOException
349 	 * @since 3.0
350 	 */
351 	public void setLastModified(File f, long time) throws IOException {
352 		FileUtils.setLastModified(f, time);
353 	}
354 
355 	/**
356 	 * Get the length of a file or link, If the OS/JRE supports symbolic links
357 	 * it's the length of the link, else the length of the target.
358 	 *
359 	 * @param path
360 	 *            a {@link java.io.File} object.
361 	 * @return length of a file
362 	 * @throws java.io.IOException
363 	 * @since 3.0
364 	 */
365 	public long length(File path) throws IOException {
366 		return FileUtils.getLength(path);
367 	}
368 
369 	/**
370 	 * Delete a file. Throws an exception if delete fails.
371 	 *
372 	 * @param f
373 	 *            a {@link java.io.File} object.
374 	 * @throws java.io.IOException
375 	 *             this may be a Java7 subclass with detailed information
376 	 * @since 3.3
377 	 */
378 	public void delete(File f) throws IOException {
379 		FileUtils.delete(f);
380 	}
381 
382 	/**
383 	 * Resolve this file to its actual path name that the JRE can use.
384 	 * <p>
385 	 * This method can be relatively expensive. Computing a translation may
386 	 * require forking an external process per path name translated. Callers
387 	 * should try to minimize the number of translations necessary by caching
388 	 * the results.
389 	 * <p>
390 	 * Not all platforms and JREs require path name translation. Currently only
391 	 * Cygwin on Win32 require translation for Cygwin based paths.
392 	 *
393 	 * @param dir
394 	 *            directory relative to which the path name is.
395 	 * @param name
396 	 *            path name to translate.
397 	 * @return the translated path. <code>new File(dir,name)</code> if this
398 	 *         platform does not require path name translation.
399 	 */
400 	public File resolve(File dir, String name) {
401 		final File abspn = new File(name);
402 		if (abspn.isAbsolute())
403 			return abspn;
404 		return new File(dir, name);
405 	}
406 
407 	/**
408 	 * Determine the user's home directory (location where preferences are).
409 	 * <p>
410 	 * This method can be expensive on the first invocation if path name
411 	 * translation is required. Subsequent invocations return a cached result.
412 	 * <p>
413 	 * Not all platforms and JREs require path name translation. Currently only
414 	 * Cygwin on Win32 requires translation of the Cygwin HOME directory.
415 	 *
416 	 * @return the user's home directory; null if the user does not have one.
417 	 */
418 	public File userHome() {
419 		Holder<File> p = userHome;
420 		if (p == null) {
421 			p = new Holder<>(userHomeImpl());
422 			userHome = p;
423 		}
424 		return p.value;
425 	}
426 
427 	/**
428 	 * Set the user's home directory location.
429 	 *
430 	 * @param path
431 	 *            the location of the user's preferences; null if there is no
432 	 *            home directory for the current user.
433 	 * @return {@code this}.
434 	 */
435 	public FS setUserHome(File path) {
436 		userHome = new Holder<>(path);
437 		return this;
438 	}
439 
440 	/**
441 	 * Does this file system have problems with atomic renames?
442 	 *
443 	 * @return true if the caller should retry a failed rename of a lock file.
444 	 */
445 	public abstract boolean retryFailedLockFileCommit();
446 
447 	/**
448 	 * Return all the attributes of a file, without following symbolic links.
449 	 *
450 	 * @param file
451 	 * @return {@link BasicFileAttributes} of the file
452 	 * @throws IOException in case of any I/O errors accessing the file
453 	 *
454 	 * @since 4.5.6
455 	 */
456 	public BasicFileAttributes fileAttributes(File file) throws IOException {
457 		return FileUtils.fileAttributes(file);
458 	}
459 
460 	/**
461 	 * Determine the user's home directory (location where preferences are).
462 	 *
463 	 * @return the user's home directory; null if the user does not have one.
464 	 */
465 	protected File userHomeImpl() {
466 		final String home = AccessController
467 				.doPrivileged(new PrivilegedAction<String>() {
468 					@Override
469 					public String run() {
470 						return System.getProperty("user.home"); //$NON-NLS-1$
471 					}
472 				});
473 		if (home == null || home.length() == 0)
474 			return null;
475 		return new File(home).getAbsoluteFile();
476 	}
477 
478 	/**
479 	 * Searches the given path to see if it contains one of the given files.
480 	 * Returns the first it finds. Returns null if not found or if path is null.
481 	 *
482 	 * @param path
483 	 *            List of paths to search separated by File.pathSeparator
484 	 * @param lookFor
485 	 *            Files to search for in the given path
486 	 * @return the first match found, or null
487 	 * @since 3.0
488 	 */
489 	protected static File searchPath(String path, String... lookFor) {
490 		if (path == null)
491 			return null;
492 
493 		for (String p : path.split(File.pathSeparator)) {
494 			for (String command : lookFor) {
495 				final File e = new File(p, command);
496 				if (e.isFile())
497 					return e.getAbsoluteFile();
498 			}
499 		}
500 		return null;
501 	}
502 
503 	/**
504 	 * Execute a command and return a single line of output as a String
505 	 *
506 	 * @param dir
507 	 *            Working directory for the command
508 	 * @param command
509 	 *            as component array
510 	 * @param encoding
511 	 *            to be used to parse the command's output
512 	 * @return the one-line output of the command or {@code null} if there is
513 	 *         none
514 	 * @throws org.eclipse.jgit.errors.CommandFailedException
515 	 *             thrown when the command failed (return code was non-zero)
516 	 */
517 	@Nullable
518 	protected static String readPipe(File dir, String[] command,
519 			String encoding) throws CommandFailedException {
520 		return readPipe(dir, command, encoding, null);
521 	}
522 
523 	/**
524 	 * Execute a command and return a single line of output as a String
525 	 *
526 	 * @param dir
527 	 *            Working directory for the command
528 	 * @param command
529 	 *            as component array
530 	 * @param encoding
531 	 *            to be used to parse the command's output
532 	 * @param env
533 	 *            Map of environment variables to be merged with those of the
534 	 *            current process
535 	 * @return the one-line output of the command or {@code null} if there is
536 	 *         none
537 	 * @throws org.eclipse.jgit.errors.CommandFailedException
538 	 *             thrown when the command failed (return code was non-zero)
539 	 * @since 4.0
540 	 */
541 	@Nullable
542 	protected static String readPipe(File dir, String[] command,
543 			String encoding, Map<String, String> env)
544 			throws CommandFailedException {
545 		final boolean debug = LOG.isDebugEnabled();
546 		try {
547 			if (debug) {
548 				LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
549 						+ dir);
550 			}
551 			ProcessBuilder pb = new ProcessBuilder(command);
552 			pb.directory(dir);
553 			if (env != null) {
554 				pb.environment().putAll(env);
555 			}
556 			Process p;
557 			try {
558 				p = pb.start();
559 			} catch (IOException e) {
560 				// Process failed to start
561 				throw new CommandFailedException(-1, e.getMessage(), e);
562 			}
563 			p.getOutputStream().close();
564 			GobblerThread gobbler = new GobblerThread(p, command, dir);
565 			gobbler.start();
566 			String r = null;
567 			try (BufferedReader lineRead = new BufferedReader(
568 					new InputStreamReader(p.getInputStream(), encoding))) {
569 				r = lineRead.readLine();
570 				if (debug) {
571 					LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
572 					LOG.debug("remaining output:\n"); //$NON-NLS-1$
573 					String l;
574 					while ((l = lineRead.readLine()) != null) {
575 						LOG.debug(l);
576 					}
577 				}
578 			}
579 
580 			for (;;) {
581 				try {
582 					int rc = p.waitFor();
583 					gobbler.join();
584 					if (rc == 0 && !gobbler.fail.get()) {
585 						return r;
586 					} else {
587 						if (debug) {
588 							LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
589 						}
590 						throw new CommandFailedException(rc,
591 								gobbler.errorMessage.get(),
592 								gobbler.exception.get());
593 					}
594 				} catch (InterruptedException ie) {
595 					// Stop bothering me, I have a zombie to reap.
596 				}
597 			}
598 		} catch (IOException e) {
599 			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
600 		}
601 		if (debug) {
602 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
603 		}
604 		return null;
605 	}
606 
607 	private static class GobblerThread extends Thread {
608 
609 		/* The process has 5 seconds to exit after closing stderr */
610 		private static final int PROCESS_EXIT_TIMEOUT = 5;
611 
612 		private final Process p;
613 		private final String desc;
614 		private final String dir;
615 		final AtomicBoolean fail = new AtomicBoolean();
616 		final AtomicReference<String> errorMessage = new AtomicReference<>();
617 		final AtomicReference<Throwable> exception = new AtomicReference<>();
618 
619 		GobblerThread(Process p, String[] command, File dir) {
620 			this.p = p;
621 			this.desc = Arrays.toString(command);
622 			this.dir = Objects.toString(dir);
623 		}
624 
625 		@Override
626 		public void run() {
627 			StringBuilder err = new StringBuilder();
628 			try (InputStream is = p.getErrorStream()) {
629 				int ch;
630 				while ((ch = is.read()) != -1) {
631 					err.append((char) ch);
632 				}
633 			} catch (IOException e) {
634 				if (waitForProcessCompletion(e) && p.exitValue() != 0) {
635 					setError(e, e.getMessage(), p.exitValue());
636 					fail.set(true);
637 				} else {
638 					// ignore. command terminated faster and stream was just closed
639 					// or the process didn't terminate within timeout
640 				}
641 			} finally {
642 				if (waitForProcessCompletion(null) && err.length() > 0) {
643 					setError(null, err.toString(), p.exitValue());
644 					if (p.exitValue() != 0) {
645 						fail.set(true);
646 					}
647 				}
648 			}
649 		}
650 
651 		@SuppressWarnings("boxing")
652 		private boolean waitForProcessCompletion(IOException originalError) {
653 			try {
654 				if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
655 					setError(originalError, MessageFormat.format(
656 							JGitText.get().commandClosedStderrButDidntExit,
657 							desc, PROCESS_EXIT_TIMEOUT), -1);
658 					fail.set(true);
659 					return false;
660 				}
661 			} catch (InterruptedException e) {
662 				setError(originalError, MessageFormat.format(
663 						JGitText.get().threadInterruptedWhileRunning, desc), -1);
664 				fail.set(true);
665 				return false;
666 			}
667 			return true;
668 		}
669 
670 		private void setError(IOException e, String message, int exitCode) {
671 			exception.set(e);
672 			errorMessage.set(MessageFormat.format(
673 					JGitText.get().exceptionCaughtDuringExecutionOfCommand,
674 					desc, dir, Integer.valueOf(exitCode), message));
675 		}
676 	}
677 
678 	/**
679 	 * Discover the path to the Git executable.
680 	 *
681 	 * @return the path to the Git executable or {@code null} if it cannot be
682 	 *         determined.
683 	 * @since 4.0
684 	 */
685 	protected abstract File discoverGitExe();
686 
687 	/**
688 	 * Discover the path to the system-wide Git configuration file
689 	 *
690 	 * @return the path to the system-wide Git configuration file or
691 	 *         {@code null} if it cannot be determined.
692 	 * @since 4.0
693 	 */
694 	protected File discoverGitSystemConfig() {
695 		File gitExe = discoverGitExe();
696 		if (gitExe == null) {
697 			return null;
698 		}
699 
700 		// Bug 480782: Check if the discovered git executable is JGit CLI
701 		String v;
702 		try {
703 			v = readPipe(gitExe.getParentFile(),
704 				new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
705 				Charset.defaultCharset().name());
706 		} catch (CommandFailedException e) {
707 			LOG.warn(e.getMessage());
708 			return null;
709 		}
710 		if (StringUtils.isEmptyOrNull(v)
711 				|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
712 			return null;
713 		}
714 
715 		// Trick Git into printing the path to the config file by using "echo"
716 		// as the editor.
717 		Map<String, String> env = new HashMap<>();
718 		env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
719 
720 		String w;
721 		try {
722 			w = readPipe(gitExe.getParentFile(),
723 				new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
724 				Charset.defaultCharset().name(), env);
725 		} catch (CommandFailedException e) {
726 			LOG.warn(e.getMessage());
727 			return null;
728 		}
729 		if (StringUtils.isEmptyOrNull(w)) {
730 			return null;
731 		}
732 
733 		return new File(w);
734 	}
735 
736 	/**
737 	 * Get the currently used path to the system-wide Git configuration file.
738 	 *
739 	 * @return the currently used path to the system-wide Git configuration file
740 	 *         or {@code null} if none has been set.
741 	 * @since 4.0
742 	 */
743 	public File getGitSystemConfig() {
744 		if (gitSystemConfig == null) {
745 			gitSystemConfig = new Holder<>(discoverGitSystemConfig());
746 		}
747 		return gitSystemConfig.value;
748 	}
749 
750 	/**
751 	 * Set the path to the system-wide Git configuration file to use.
752 	 *
753 	 * @param configFile
754 	 *            the path to the config file.
755 	 * @return {@code this}
756 	 * @since 4.0
757 	 */
758 	public FS setGitSystemConfig(File configFile) {
759 		gitSystemConfig = new Holder<>(configFile);
760 		return this;
761 	}
762 
763 	/**
764 	 * Get the parent directory of this file's parent directory
765 	 *
766 	 * @param grandchild
767 	 *            a {@link java.io.File} object.
768 	 * @return the parent directory of this file's parent directory or
769 	 *         {@code null} in case there's no grandparent directory
770 	 * @since 4.0
771 	 */
772 	protected static File resolveGrandparentFile(File grandchild) {
773 		if (grandchild != null) {
774 			File parent = grandchild.getParentFile();
775 			if (parent != null)
776 				return parent.getParentFile();
777 		}
778 		return null;
779 	}
780 
781 	/**
782 	 * Check if a file is a symbolic link and read it
783 	 *
784 	 * @param path
785 	 *            a {@link java.io.File} object.
786 	 * @return target of link or null
787 	 * @throws java.io.IOException
788 	 * @since 3.0
789 	 */
790 	public String readSymLink(File path) throws IOException {
791 		return FileUtils.readSymLink(path);
792 	}
793 
794 	/**
795 	 * Whether the path is a symbolic link (and we support these).
796 	 *
797 	 * @param path
798 	 *            a {@link java.io.File} object.
799 	 * @return true if the path is a symbolic link (and we support these)
800 	 * @throws java.io.IOException
801 	 * @since 3.0
802 	 */
803 	public boolean isSymLink(File path) throws IOException {
804 		return FileUtils.isSymlink(path);
805 	}
806 
807 	/**
808 	 * Tests if the path exists, in case of a symbolic link, true even if the
809 	 * target does not exist
810 	 *
811 	 * @param path
812 	 *            a {@link java.io.File} object.
813 	 * @return true if path exists
814 	 * @since 3.0
815 	 */
816 	public boolean exists(File path) {
817 		return FileUtils.exists(path);
818 	}
819 
820 	/**
821 	 * Check if path is a directory. If the OS/JRE supports symbolic links and
822 	 * path is a symbolic link to a directory, this method returns false.
823 	 *
824 	 * @param path
825 	 *            a {@link java.io.File} object.
826 	 * @return true if file is a directory,
827 	 * @since 3.0
828 	 */
829 	public boolean isDirectory(File path) {
830 		return FileUtils.isDirectory(path);
831 	}
832 
833 	/**
834 	 * Examine if path represents a regular file. If the OS/JRE supports
835 	 * symbolic links the test returns false if path represents a symbolic link.
836 	 *
837 	 * @param path
838 	 *            a {@link java.io.File} object.
839 	 * @return true if path represents a regular file
840 	 * @since 3.0
841 	 */
842 	public boolean isFile(File path) {
843 		return FileUtils.isFile(path);
844 	}
845 
846 	/**
847 	 * Whether path is hidden, either starts with . on unix or has the hidden
848 	 * attribute in windows
849 	 *
850 	 * @param path
851 	 *            a {@link java.io.File} object.
852 	 * @return true if path is hidden, either starts with . on unix or has the
853 	 *         hidden attribute in windows
854 	 * @throws java.io.IOException
855 	 * @since 3.0
856 	 */
857 	public boolean isHidden(File path) throws IOException {
858 		return FileUtils.isHidden(path);
859 	}
860 
861 	/**
862 	 * Set the hidden attribute for file whose name starts with a period.
863 	 *
864 	 * @param path
865 	 *            a {@link java.io.File} object.
866 	 * @param hidden
867 	 *            whether to set the file hidden
868 	 * @throws java.io.IOException
869 	 * @since 3.0
870 	 */
871 	public void setHidden(File path, boolean hidden) throws IOException {
872 		FileUtils.setHidden(path, hidden);
873 	}
874 
875 	/**
876 	 * Create a symbolic link
877 	 *
878 	 * @param path
879 	 *            a {@link java.io.File} object.
880 	 * @param target
881 	 *            target path of the symlink
882 	 * @throws java.io.IOException
883 	 * @since 3.0
884 	 */
885 	public void createSymLink(File path, String target) throws IOException {
886 		FileUtils.createSymLink(path, target);
887 	}
888 
889 	/**
890 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
891 	 * of this class may take care to provide a safe implementation for this
892 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
893 	 *
894 	 * @param path
895 	 *            the file to be created
896 	 * @return <code>true</code> if the file was created, <code>false</code> if
897 	 *         the file already existed
898 	 * @throws java.io.IOException
899 	 * @deprecated use {@link #createNewFileAtomic(File)} instead
900 	 * @since 4.5
901 	 */
902 	@Deprecated
903 	public boolean createNewFile(File path) throws IOException {
904 		return path.createNewFile();
905 	}
906 
907 	/**
908 	 * A token representing a file created by
909 	 * {@link #createNewFileAtomic(File)}. The token must be retained until the
910 	 * file has been deleted in order to guarantee that the unique file was
911 	 * created atomically. As soon as the file is no longer needed the lock
912 	 * token must be closed.
913 	 *
914 	 * @since 4.7
915 	 */
916 	public static class LockToken implements Closeable {
917 		private boolean isCreated;
918 
919 		private Optional<Path> link;
920 
921 		LockToken(boolean isCreated, Optional<Path> link) {
922 			this.isCreated = isCreated;
923 			this.link = link;
924 		}
925 
926 		/**
927 		 * @return {@code true} if the file was created successfully
928 		 */
929 		public boolean isCreated() {
930 			return isCreated;
931 		}
932 
933 		@Override
934 		public void close() {
935 			if (!link.isPresent()) {
936 				return;
937 			}
938 			Path p = link.get();
939 			if (!Files.exists(p)) {
940 				return;
941 			}
942 			try {
943 				Files.delete(p);
944 			} catch (IOException e) {
945 				LOG.error(MessageFormat
946 						.format(JGitText.get().closeLockTokenFailed, this), e);
947 			}
948 		}
949 
950 		@Override
951 		public String toString() {
952 			return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
953 					", link=" //$NON-NLS-1$
954 					+ (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
955 							: "<null>]"); //$NON-NLS-1$
956 		}
957 	}
958 
959 	/**
960 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
961 	 * of this class may take care to provide a safe implementation for this
962 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
963 	 *
964 	 * @param path
965 	 *            the file to be created
966 	 * @return LockToken this token must be closed after the created file was
967 	 *         deleted
968 	 * @throws IOException
969 	 * @since 4.7
970 	 */
971 	public LockToken createNewFileAtomic(File path) throws IOException {
972 		return new LockToken(path.createNewFile(), Optional.empty());
973 	}
974 
975 	/**
976 	 * See
977 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
978 	 *
979 	 * @param base
980 	 *            The path against which <code>other</code> should be
981 	 *            relativized.
982 	 * @param other
983 	 *            The path that will be made relative to <code>base</code>.
984 	 * @return A relative path that, when resolved against <code>base</code>,
985 	 *         will yield the original <code>other</code>.
986 	 * @see FileUtils#relativizePath(String, String, String, boolean)
987 	 * @since 3.7
988 	 */
989 	public String relativize(String base, String other) {
990 		return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
991 	}
992 
993 	/**
994 	 * Enumerates children of a directory.
995 	 *
996 	 * @param directory
997 	 *            to get the children of
998 	 * @param fileModeStrategy
999 	 *            to use to calculate the git mode of a child
1000 	 * @return an array of entries for the children
1001 	 *
1002 	 * @since 5.0
1003 	 */
1004 	public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1005 		final File[] all = directory.listFiles();
1006 		if (all == null) {
1007 			return NO_ENTRIES;
1008 		}
1009 		final Entry[] result = new Entry[all.length];
1010 		for (int i = 0; i < result.length; i++) {
1011 			result[i] = new FileEntry(all[i], this, fileModeStrategy);
1012 		}
1013 		return result;
1014 	}
1015 
1016 	/**
1017 	 * Checks whether the given hook is defined for the given repository, then
1018 	 * runs it with the given arguments.
1019 	 * <p>
1020 	 * The hook's standard output and error streams will be redirected to
1021 	 * <code>System.out</code> and <code>System.err</code> respectively. The
1022 	 * hook will have no stdin.
1023 	 * </p>
1024 	 *
1025 	 * @param repository
1026 	 *            The repository for which a hook should be run.
1027 	 * @param hookName
1028 	 *            The name of the hook to be executed.
1029 	 * @param args
1030 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1031 	 *            but can be an empty array.
1032 	 * @return The ProcessResult describing this hook's execution.
1033 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1034 	 *             if we fail to run the hook somehow. Causes may include an
1035 	 *             interrupted process or I/O errors.
1036 	 * @since 4.0
1037 	 */
1038 	public ProcessResult runHookIfPresent(Repository repository,
1039 			final String hookName,
1040 			String[] args) throws JGitInternalException {
1041 		return runHookIfPresent(repository, hookName, args, System.out, System.err,
1042 				null);
1043 	}
1044 
1045 	/**
1046 	 * Checks whether the given hook is defined for the given repository, then
1047 	 * runs it with the given arguments.
1048 	 *
1049 	 * @param repository
1050 	 *            The repository for which a hook should be run.
1051 	 * @param hookName
1052 	 *            The name of the hook to be executed.
1053 	 * @param args
1054 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1055 	 *            but can be an empty array.
1056 	 * @param outRedirect
1057 	 *            A print stream on which to redirect the hook's stdout. Can be
1058 	 *            <code>null</code>, in which case the hook's standard output
1059 	 *            will be lost.
1060 	 * @param errRedirect
1061 	 *            A print stream on which to redirect the hook's stderr. Can be
1062 	 *            <code>null</code>, in which case the hook's standard error
1063 	 *            will be lost.
1064 	 * @param stdinArgs
1065 	 *            A string to pass on to the standard input of the hook. May be
1066 	 *            <code>null</code>.
1067 	 * @return The ProcessResult describing this hook's execution.
1068 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1069 	 *             if we fail to run the hook somehow. Causes may include an
1070 	 *             interrupted process or I/O errors.
1071 	 * @since 4.0
1072 	 */
1073 	public ProcessResult runHookIfPresent(Repository repository,
1074 			final String hookName,
1075 			String[] args, PrintStream outRedirect, PrintStream errRedirect,
1076 			String stdinArgs) throws JGitInternalException {
1077 		return new ProcessResult(Status.NOT_SUPPORTED);
1078 	}
1079 
1080 	/**
1081 	 * See
1082 	 * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
1083 	 * . Should only be called by FS supporting shell scripts execution.
1084 	 *
1085 	 * @param repository
1086 	 *            The repository for which a hook should be run.
1087 	 * @param hookName
1088 	 *            The name of the hook to be executed.
1089 	 * @param args
1090 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1091 	 *            but can be an empty array.
1092 	 * @param outRedirect
1093 	 *            A print stream on which to redirect the hook's stdout. Can be
1094 	 *            <code>null</code>, in which case the hook's standard output
1095 	 *            will be lost.
1096 	 * @param errRedirect
1097 	 *            A print stream on which to redirect the hook's stderr. Can be
1098 	 *            <code>null</code>, in which case the hook's standard error
1099 	 *            will be lost.
1100 	 * @param stdinArgs
1101 	 *            A string to pass on to the standard input of the hook. May be
1102 	 *            <code>null</code>.
1103 	 * @return The ProcessResult describing this hook's execution.
1104 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1105 	 *             if we fail to run the hook somehow. Causes may include an
1106 	 *             interrupted process or I/O errors.
1107 	 * @since 4.0
1108 	 */
1109 	protected ProcessResult internalRunHookIfPresent(Repository repository,
1110 			final String hookName, String[] args, PrintStream outRedirect,
1111 			PrintStream errRedirect, String stdinArgs)
1112 			throws JGitInternalException {
1113 		final File hookFile = findHook(repository, hookName);
1114 		if (hookFile == null)
1115 			return new ProcessResult(Status.NOT_PRESENT);
1116 
1117 		final String hookPath = hookFile.getAbsolutePath();
1118 		final File runDirectory;
1119 		if (repository.isBare())
1120 			runDirectory = repository.getDirectory();
1121 		else
1122 			runDirectory = repository.getWorkTree();
1123 		final String cmd = relativize(runDirectory.getAbsolutePath(),
1124 				hookPath);
1125 		ProcessBuilder hookProcess = runInShell(cmd, args);
1126 		hookProcess.directory(runDirectory);
1127 		Map<String, String> environment = hookProcess.environment();
1128 		environment.put(Constants.GIT_DIR_KEY,
1129 				repository.getDirectory().getAbsolutePath());
1130 		if (!repository.isBare()) {
1131 			environment.put(Constants.GIT_WORK_TREE_KEY,
1132 					repository.getWorkTree().getAbsolutePath());
1133 		}
1134 		try {
1135 			return new ProcessResult(runProcess(hookProcess, outRedirect,
1136 					errRedirect, stdinArgs), Status.OK);
1137 		} catch (IOException e) {
1138 			throw new JGitInternalException(MessageFormat.format(
1139 					JGitText.get().exceptionCaughtDuringExecutionOfHook,
1140 					hookName), e);
1141 		} catch (InterruptedException e) {
1142 			throw new JGitInternalException(MessageFormat.format(
1143 					JGitText.get().exceptionHookExecutionInterrupted,
1144 							hookName), e);
1145 		}
1146 	}
1147 
1148 
1149 	/**
1150 	 * Tries to find a hook matching the given one in the given repository.
1151 	 *
1152 	 * @param repository
1153 	 *            The repository within which to find a hook.
1154 	 * @param hookName
1155 	 *            The name of the hook we're trying to find.
1156 	 * @return The {@link java.io.File} containing this particular hook if it
1157 	 *         exists in the given repository, <code>null</code> otherwise.
1158 	 * @since 4.0
1159 	 */
1160 	public File findHook(Repository repository, String hookName) {
1161 		File gitDir = repository.getDirectory();
1162 		if (gitDir == null)
1163 			return null;
1164 		final File hookFile = new File(new File(gitDir,
1165 				Constants.HOOKS), hookName);
1166 		return hookFile.isFile() ? hookFile : null;
1167 	}
1168 
1169 	/**
1170 	 * Runs the given process until termination, clearing its stdout and stderr
1171 	 * streams on-the-fly.
1172 	 *
1173 	 * @param processBuilder
1174 	 *            The process builder configured for this process.
1175 	 * @param outRedirect
1176 	 *            A OutputStream on which to redirect the processes stdout. Can
1177 	 *            be <code>null</code>, in which case the processes standard
1178 	 *            output will be lost.
1179 	 * @param errRedirect
1180 	 *            A OutputStream on which to redirect the processes stderr. Can
1181 	 *            be <code>null</code>, in which case the processes standard
1182 	 *            error will be lost.
1183 	 * @param stdinArgs
1184 	 *            A string to pass on to the standard input of the hook. Can be
1185 	 *            <code>null</code>.
1186 	 * @return the exit value of this process.
1187 	 * @throws java.io.IOException
1188 	 *             if an I/O error occurs while executing this process.
1189 	 * @throws java.lang.InterruptedException
1190 	 *             if the current thread is interrupted while waiting for the
1191 	 *             process to end.
1192 	 * @since 4.2
1193 	 */
1194 	public int runProcess(ProcessBuilder processBuilder,
1195 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1196 			throws IOException, InterruptedException {
1197 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1198 						stdinArgs.getBytes(UTF_8));
1199 		return runProcess(processBuilder, outRedirect, errRedirect, in);
1200 	}
1201 
1202 	/**
1203 	 * Runs the given process until termination, clearing its stdout and stderr
1204 	 * streams on-the-fly.
1205 	 *
1206 	 * @param processBuilder
1207 	 *            The process builder configured for this process.
1208 	 * @param outRedirect
1209 	 *            An OutputStream on which to redirect the processes stdout. Can
1210 	 *            be <code>null</code>, in which case the processes standard
1211 	 *            output will be lost.
1212 	 * @param errRedirect
1213 	 *            An OutputStream on which to redirect the processes stderr. Can
1214 	 *            be <code>null</code>, in which case the processes standard
1215 	 *            error will be lost.
1216 	 * @param inRedirect
1217 	 *            An InputStream from which to redirect the processes stdin. Can
1218 	 *            be <code>null</code>, in which case the process doesn't get
1219 	 *            any data over stdin. It is assumed that the whole InputStream
1220 	 *            will be consumed by the process. The method will close the
1221 	 *            inputstream after all bytes are read.
1222 	 * @return the return code of this process.
1223 	 * @throws java.io.IOException
1224 	 *             if an I/O error occurs while executing this process.
1225 	 * @throws java.lang.InterruptedException
1226 	 *             if the current thread is interrupted while waiting for the
1227 	 *             process to end.
1228 	 * @since 4.2
1229 	 */
1230 	public int runProcess(ProcessBuilder processBuilder,
1231 			OutputStream outRedirect, OutputStream errRedirect,
1232 			InputStream inRedirect) throws IOException,
1233 			InterruptedException {
1234 		final ExecutorService executor = Executors.newFixedThreadPool(2);
1235 		Process process = null;
1236 		// We'll record the first I/O exception that occurs, but keep on trying
1237 		// to dispose of our open streams and file handles
1238 		IOException ioException = null;
1239 		try {
1240 			process = processBuilder.start();
1241 			executor.execute(
1242 					new StreamGobbler(process.getErrorStream(), errRedirect));
1243 			executor.execute(
1244 					new StreamGobbler(process.getInputStream(), outRedirect));
1245 			@SuppressWarnings("resource") // Closed in the finally block
1246 			OutputStream outputStream = process.getOutputStream();
1247 			try {
1248 				if (inRedirect != null) {
1249 					new StreamGobbler(inRedirect, outputStream).copy();
1250 				}
1251 			} finally {
1252 				try {
1253 					outputStream.close();
1254 				} catch (IOException e) {
1255 					// When the process exits before consuming the input, the OutputStream
1256 					// is replaced with the null output stream. This null output stream
1257 					// throws IOException for all write calls. When StreamGobbler fails to
1258 					// flush the buffer because of this, this close call tries to flush it
1259 					// again. This causes another IOException. Since we ignore the
1260 					// IOException in StreamGobbler, we also ignore the exception here.
1261 				}
1262 			}
1263 			return process.waitFor();
1264 		} catch (IOException e) {
1265 			ioException = e;
1266 		} finally {
1267 			shutdownAndAwaitTermination(executor);
1268 			if (process != null) {
1269 				try {
1270 					process.waitFor();
1271 				} catch (InterruptedException e) {
1272 					// Thrown by the outer try.
1273 					// Swallow this one to carry on our cleanup, and clear the
1274 					// interrupted flag (processes throw the exception without
1275 					// clearing the flag).
1276 					Thread.interrupted();
1277 				}
1278 				// A process doesn't clean its own resources even when destroyed
1279 				// Explicitly try and close all three streams, preserving the
1280 				// outer I/O exception if any.
1281 				if (inRedirect != null) {
1282 					inRedirect.close();
1283 				}
1284 				try {
1285 					process.getErrorStream().close();
1286 				} catch (IOException e) {
1287 					ioException = ioException != null ? ioException : e;
1288 				}
1289 				try {
1290 					process.getInputStream().close();
1291 				} catch (IOException e) {
1292 					ioException = ioException != null ? ioException : e;
1293 				}
1294 				try {
1295 					process.getOutputStream().close();
1296 				} catch (IOException e) {
1297 					ioException = ioException != null ? ioException : e;
1298 				}
1299 				process.destroy();
1300 			}
1301 		}
1302 		// We can only be here if the outer try threw an IOException.
1303 		throw ioException;
1304 	}
1305 
1306 	/**
1307 	 * Shuts down an {@link ExecutorService} in two phases, first by calling
1308 	 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
1309 	 * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
1310 	 * necessary, to cancel any lingering tasks. Returns true if the pool has
1311 	 * been properly shutdown, false otherwise.
1312 	 * <p>
1313 	 *
1314 	 * @param pool
1315 	 *            the pool to shutdown
1316 	 * @return <code>true</code> if the pool has been properly shutdown,
1317 	 *         <code>false</code> otherwise.
1318 	 */
1319 	private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1320 		boolean hasShutdown = true;
1321 		pool.shutdown(); // Disable new tasks from being submitted
1322 		try {
1323 			// Wait a while for existing tasks to terminate
1324 			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1325 				pool.shutdownNow(); // Cancel currently executing tasks
1326 				// Wait a while for tasks to respond to being canceled
1327 				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1328 					hasShutdown = false;
1329 			}
1330 		} catch (InterruptedException ie) {
1331 			// (Re-)Cancel if current thread also interrupted
1332 			pool.shutdownNow();
1333 			// Preserve interrupt status
1334 			Thread.currentThread().interrupt();
1335 			hasShutdown = false;
1336 		}
1337 		return hasShutdown;
1338 	}
1339 
1340 	/**
1341 	 * Initialize a ProcessBuilder to run a command using the system shell.
1342 	 *
1343 	 * @param cmd
1344 	 *            command to execute. This string should originate from the
1345 	 *            end-user, and thus is platform specific.
1346 	 * @param args
1347 	 *            arguments to pass to command. These should be protected from
1348 	 *            shell evaluation.
1349 	 * @return a partially completed process builder. Caller should finish
1350 	 *         populating directory, environment, and then start the process.
1351 	 */
1352 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
1353 
1354 	/**
1355 	 * Execute a command defined by a {@link java.lang.ProcessBuilder}.
1356 	 *
1357 	 * @param pb
1358 	 *            The command to be executed
1359 	 * @param in
1360 	 *            The standard input stream passed to the process
1361 	 * @return The result of the executed command
1362 	 * @throws java.lang.InterruptedException
1363 	 * @throws java.io.IOException
1364 	 * @since 4.2
1365 	 */
1366 	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1367 			throws IOException, InterruptedException {
1368 		try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1369 				TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1370 						1024 * 1024)) {
1371 			int rc = runProcess(pb, stdout, stderr, in);
1372 			return new ExecutionResult(stdout, stderr, rc);
1373 		}
1374 	}
1375 
1376 	private static class Holder<V> {
1377 		final V value;
1378 
1379 		Holder(V value) {
1380 			this.value = value;
1381 		}
1382 	}
1383 
1384 	/**
1385 	 * File attributes we typically care for.
1386 	 *
1387 	 * @since 3.3
1388 	 */
1389 	public static class Attributes {
1390 
1391 		/**
1392 		 * @return true if this are the attributes of a directory
1393 		 */
1394 		public boolean isDirectory() {
1395 			return isDirectory;
1396 		}
1397 
1398 		/**
1399 		 * @return true if this are the attributes of an executable file
1400 		 */
1401 		public boolean isExecutable() {
1402 			return isExecutable;
1403 		}
1404 
1405 		/**
1406 		 * @return true if this are the attributes of a symbolic link
1407 		 */
1408 		public boolean isSymbolicLink() {
1409 			return isSymbolicLink;
1410 		}
1411 
1412 		/**
1413 		 * @return true if this are the attributes of a regular file
1414 		 */
1415 		public boolean isRegularFile() {
1416 			return isRegularFile;
1417 		}
1418 
1419 		/**
1420 		 * @return the time when the file was created
1421 		 */
1422 		public long getCreationTime() {
1423 			return creationTime;
1424 		}
1425 
1426 		/**
1427 		 * @return the time (milliseconds since 1970-01-01) when this object was
1428 		 *         last modified
1429 		 */
1430 		public long getLastModifiedTime() {
1431 			return lastModifiedTime;
1432 		}
1433 
1434 		private final boolean isDirectory;
1435 
1436 		private final boolean isSymbolicLink;
1437 
1438 		private final boolean isRegularFile;
1439 
1440 		private final long creationTime;
1441 
1442 		private final long lastModifiedTime;
1443 
1444 		private final boolean isExecutable;
1445 
1446 		private final File file;
1447 
1448 		private final boolean exists;
1449 
1450 		/**
1451 		 * file length
1452 		 */
1453 		protected long length = -1;
1454 
1455 		final FS fs;
1456 
1457 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1458 				boolean isExecutable, boolean isSymbolicLink,
1459 				boolean isRegularFile, long creationTime,
1460 				long lastModifiedTime, long length) {
1461 			this.fs = fs;
1462 			this.file = file;
1463 			this.exists = exists;
1464 			this.isDirectory = isDirectory;
1465 			this.isExecutable = isExecutable;
1466 			this.isSymbolicLink = isSymbolicLink;
1467 			this.isRegularFile = isRegularFile;
1468 			this.creationTime = creationTime;
1469 			this.lastModifiedTime = lastModifiedTime;
1470 			this.length = length;
1471 		}
1472 
1473 		/**
1474 		 * Constructor when there are issues with reading. All attributes except
1475 		 * given will be set to the default values.
1476 		 *
1477 		 * @param fs
1478 		 * @param path
1479 		 */
1480 		public Attributes(File path, FS fs) {
1481 			this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1482 		}
1483 
1484 		/**
1485 		 * @return length of this file object
1486 		 */
1487 		public long getLength() {
1488 			if (length == -1)
1489 				return length = file.length();
1490 			return length;
1491 		}
1492 
1493 		/**
1494 		 * @return the filename
1495 		 */
1496 		public String getName() {
1497 			return file.getName();
1498 		}
1499 
1500 		/**
1501 		 * @return the file the attributes apply to
1502 		 */
1503 		public File getFile() {
1504 			return file;
1505 		}
1506 
1507 		boolean exists() {
1508 			return exists;
1509 		}
1510 	}
1511 
1512 	/**
1513 	 * Get the file attributes we care for.
1514 	 *
1515 	 * @param path
1516 	 *            a {@link java.io.File} object.
1517 	 * @return the file attributes we care for.
1518 	 * @since 3.3
1519 	 */
1520 	public Attributes getAttributes(File path) {
1521 		boolean isDirectory = isDirectory(path);
1522 		boolean isFile = !isDirectory && path.isFile();
1523 		assert path.exists() == isDirectory || isFile;
1524 		boolean exists = isDirectory || isFile;
1525 		boolean canExecute = exists && !isDirectory && canExecute(path);
1526 		boolean isSymlink = false;
1527 		long lastModified = exists ? path.lastModified() : 0L;
1528 		long createTime = 0L;
1529 		return new Attributes(this, path, exists, isDirectory, canExecute,
1530 				isSymlink, isFile, createTime, lastModified, -1);
1531 	}
1532 
1533 	/**
1534 	 * Normalize the unicode path to composed form.
1535 	 *
1536 	 * @param file
1537 	 *            a {@link java.io.File} object.
1538 	 * @return NFC-format File
1539 	 * @since 3.3
1540 	 */
1541 	public File normalize(File file) {
1542 		return file;
1543 	}
1544 
1545 	/**
1546 	 * Normalize the unicode path to composed form.
1547 	 *
1548 	 * @param name
1549 	 *            path name
1550 	 * @return NFC-format string
1551 	 * @since 3.3
1552 	 */
1553 	public String normalize(String name) {
1554 		return name;
1555 	}
1556 
1557 	/**
1558 	 * This runnable will consume an input stream's content into an output
1559 	 * stream as soon as it gets available.
1560 	 * <p>
1561 	 * Typically used to empty processes' standard output and error, preventing
1562 	 * them to choke.
1563 	 * </p>
1564 	 * <p>
1565 	 * <b>Note</b> that a {@link StreamGobbler} will never close either of its
1566 	 * streams.
1567 	 * </p>
1568 	 */
1569 	private static class StreamGobbler implements Runnable {
1570 		private InputStream in;
1571 
1572 		private OutputStream out;
1573 
1574 		public StreamGobbler(InputStream stream, OutputStream output) {
1575 			this.in = stream;
1576 			this.out = output;
1577 		}
1578 
1579 		@Override
1580 		public void run() {
1581 			try {
1582 				copy();
1583 			} catch (IOException e) {
1584 				// Do nothing on read failure; leave streams open.
1585 			}
1586 		}
1587 
1588 		void copy() throws IOException {
1589 			boolean writeFailure = false;
1590 			byte buffer[] = new byte[4096];
1591 			int readBytes;
1592 			while ((readBytes = in.read(buffer)) != -1) {
1593 				// Do not try to write again after a failure, but keep
1594 				// reading as long as possible to prevent the input stream
1595 				// from choking.
1596 				if (!writeFailure && out != null) {
1597 					try {
1598 						out.write(buffer, 0, readBytes);
1599 						out.flush();
1600 					} catch (IOException e) {
1601 						writeFailure = true;
1602 					}
1603 				}
1604 			}
1605 		}
1606 	}
1607 }