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