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 					return false;
644 				}
645 			} catch (InterruptedException e) {
646 				setError(originalError, MessageFormat.format(
647 						JGitText.get().threadInterruptedWhileRunning, desc), -1);
648 				fail.set(true);
649 				return false;
650 			}
651 			return true;
652 		}
653 
654 		private void setError(IOException e, String message, int exitCode) {
655 			exception.set(e);
656 			errorMessage.set(MessageFormat.format(
657 					JGitText.get().exceptionCaughtDuringExecutionOfCommand,
658 					desc, dir, Integer.valueOf(exitCode), message));
659 		}
660 	}
661 
662 	/**
663 	 * Discover the path to the Git executable.
664 	 *
665 	 * @return the path to the Git executable or {@code null} if it cannot be
666 	 *         determined.
667 	 * @since 4.0
668 	 */
669 	protected abstract File discoverGitExe();
670 
671 	/**
672 	 * Discover the path to the system-wide Git configuration file
673 	 *
674 	 * @return the path to the system-wide Git configuration file or
675 	 *         {@code null} if it cannot be determined.
676 	 * @since 4.0
677 	 */
678 	protected File discoverGitSystemConfig() {
679 		File gitExe = discoverGitExe();
680 		if (gitExe == null) {
681 			return null;
682 		}
683 
684 		// Bug 480782: Check if the discovered git executable is JGit CLI
685 		String v;
686 		try {
687 			v = readPipe(gitExe.getParentFile(),
688 				new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
689 				Charset.defaultCharset().name());
690 		} catch (CommandFailedException e) {
691 			LOG.warn(e.getMessage());
692 			return null;
693 		}
694 		if (StringUtils.isEmptyOrNull(v)
695 				|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
696 			return null;
697 		}
698 
699 		// Trick Git into printing the path to the config file by using "echo"
700 		// as the editor.
701 		Map<String, String> env = new HashMap<>();
702 		env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
703 
704 		String w;
705 		try {
706 			w = readPipe(gitExe.getParentFile(),
707 				new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
708 				Charset.defaultCharset().name(), env);
709 		} catch (CommandFailedException e) {
710 			LOG.warn(e.getMessage());
711 			return null;
712 		}
713 		if (StringUtils.isEmptyOrNull(w)) {
714 			return null;
715 		}
716 
717 		return new File(w);
718 	}
719 
720 	/**
721 	 * Get the currently used path to the system-wide Git configuration file.
722 	 *
723 	 * @return the currently used path to the system-wide Git configuration file
724 	 *         or {@code null} if none has been set.
725 	 * @since 4.0
726 	 */
727 	public File getGitSystemConfig() {
728 		if (gitSystemConfig == null) {
729 			gitSystemConfig = new Holder<>(discoverGitSystemConfig());
730 		}
731 		return gitSystemConfig.value;
732 	}
733 
734 	/**
735 	 * Set the path to the system-wide Git configuration file to use.
736 	 *
737 	 * @param configFile
738 	 *            the path to the config file.
739 	 * @return {@code this}
740 	 * @since 4.0
741 	 */
742 	public FS setGitSystemConfig(File configFile) {
743 		gitSystemConfig = new Holder<>(configFile);
744 		return this;
745 	}
746 
747 	/**
748 	 * Get the parent directory of this file's parent directory
749 	 *
750 	 * @param grandchild
751 	 *            a {@link java.io.File} object.
752 	 * @return the parent directory of this file's parent directory or
753 	 *         {@code null} in case there's no grandparent directory
754 	 * @since 4.0
755 	 */
756 	protected static File resolveGrandparentFile(File grandchild) {
757 		if (grandchild != null) {
758 			File parent = grandchild.getParentFile();
759 			if (parent != null)
760 				return parent.getParentFile();
761 		}
762 		return null;
763 	}
764 
765 	/**
766 	 * Check if a file is a symbolic link and read it
767 	 *
768 	 * @param path
769 	 *            a {@link java.io.File} object.
770 	 * @return target of link or null
771 	 * @throws java.io.IOException
772 	 * @since 3.0
773 	 */
774 	public String readSymLink(File path) throws IOException {
775 		return FileUtils.readSymLink(path);
776 	}
777 
778 	/**
779 	 * Whether the path is a symbolic link (and we support these).
780 	 *
781 	 * @param path
782 	 *            a {@link java.io.File} object.
783 	 * @return true if the path is a symbolic link (and we support these)
784 	 * @throws java.io.IOException
785 	 * @since 3.0
786 	 */
787 	public boolean isSymLink(File path) throws IOException {
788 		return FileUtils.isSymlink(path);
789 	}
790 
791 	/**
792 	 * Tests if the path exists, in case of a symbolic link, true even if the
793 	 * target does not exist
794 	 *
795 	 * @param path
796 	 *            a {@link java.io.File} object.
797 	 * @return true if path exists
798 	 * @since 3.0
799 	 */
800 	public boolean exists(File path) {
801 		return FileUtils.exists(path);
802 	}
803 
804 	/**
805 	 * Check if path is a directory. If the OS/JRE supports symbolic links and
806 	 * path is a symbolic link to a directory, this method returns false.
807 	 *
808 	 * @param path
809 	 *            a {@link java.io.File} object.
810 	 * @return true if file is a directory,
811 	 * @since 3.0
812 	 */
813 	public boolean isDirectory(File path) {
814 		return FileUtils.isDirectory(path);
815 	}
816 
817 	/**
818 	 * Examine if path represents a regular file. If the OS/JRE supports
819 	 * symbolic links the test returns false if path represents a symbolic link.
820 	 *
821 	 * @param path
822 	 *            a {@link java.io.File} object.
823 	 * @return true if path represents a regular file
824 	 * @since 3.0
825 	 */
826 	public boolean isFile(File path) {
827 		return FileUtils.isFile(path);
828 	}
829 
830 	/**
831 	 * Whether path is hidden, either starts with . on unix or has the hidden
832 	 * attribute in windows
833 	 *
834 	 * @param path
835 	 *            a {@link java.io.File} object.
836 	 * @return true if path is hidden, either starts with . on unix or has the
837 	 *         hidden attribute in windows
838 	 * @throws java.io.IOException
839 	 * @since 3.0
840 	 */
841 	public boolean isHidden(File path) throws IOException {
842 		return FileUtils.isHidden(path);
843 	}
844 
845 	/**
846 	 * Set the hidden attribute for file whose name starts with a period.
847 	 *
848 	 * @param path
849 	 *            a {@link java.io.File} object.
850 	 * @param hidden
851 	 *            whether to set the file hidden
852 	 * @throws java.io.IOException
853 	 * @since 3.0
854 	 */
855 	public void setHidden(File path, boolean hidden) throws IOException {
856 		FileUtils.setHidden(path, hidden);
857 	}
858 
859 	/**
860 	 * Create a symbolic link
861 	 *
862 	 * @param path
863 	 *            a {@link java.io.File} object.
864 	 * @param target
865 	 *            target path of the symlink
866 	 * @throws java.io.IOException
867 	 * @since 3.0
868 	 */
869 	public void createSymLink(File path, String target) throws IOException {
870 		FileUtils.createSymLink(path, target);
871 	}
872 
873 	/**
874 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
875 	 * of this class may take care to provide a safe implementation for this
876 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
877 	 *
878 	 * @param path
879 	 *            the file to be created
880 	 * @return <code>true</code> if the file was created, <code>false</code> if
881 	 *         the file already existed
882 	 * @throws java.io.IOException
883 	 * @deprecated use {@link #createNewFileAtomic(File)} instead
884 	 * @since 4.5
885 	 */
886 	@Deprecated
887 	public boolean createNewFile(File path) throws IOException {
888 		return path.createNewFile();
889 	}
890 
891 	/**
892 	 * A token representing a file created by
893 	 * {@link #createNewFileAtomic(File)}. The token must be retained until the
894 	 * file has been deleted in order to guarantee that the unique file was
895 	 * created atomically. As soon as the file is no longer needed the lock
896 	 * token must be closed.
897 	 *
898 	 * @since 4.7
899 	 */
900 	public static class LockToken implements Closeable {
901 		private boolean isCreated;
902 
903 		private Optional<Path> link;
904 
905 		LockToken(boolean isCreated, Optional<Path> link) {
906 			this.isCreated = isCreated;
907 			this.link = link;
908 		}
909 
910 		/**
911 		 * @return {@code true} if the file was created successfully
912 		 */
913 		public boolean isCreated() {
914 			return isCreated;
915 		}
916 
917 		@Override
918 		public void close() {
919 			if (!link.isPresent()) {
920 				return;
921 			}
922 			Path p = link.get();
923 			if (!Files.exists(p)) {
924 				return;
925 			}
926 			try {
927 				Files.delete(p);
928 			} catch (IOException e) {
929 				LOG.error(MessageFormat
930 						.format(JGitText.get().closeLockTokenFailed, this), e);
931 			}
932 		}
933 
934 		@Override
935 		public String toString() {
936 			return "LockToken [lockCreated=" + isCreated + //$NON-NLS-1$
937 					", link=" //$NON-NLS-1$
938 					+ (link.isPresent() ? link.get().getFileName() + "]" //$NON-NLS-1$
939 							: "<null>]"); //$NON-NLS-1$
940 		}
941 	}
942 
943 	/**
944 	 * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
945 	 * of this class may take care to provide a safe implementation for this
946 	 * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
947 	 *
948 	 * @param path
949 	 *            the file to be created
950 	 * @return LockToken this token must be closed after the created file was
951 	 *         deleted
952 	 * @throws IOException
953 	 * @since 4.7
954 	 */
955 	public LockToken createNewFileAtomic(File path) throws IOException {
956 		return new LockToken(path.createNewFile(), Optional.empty());
957 	}
958 
959 	/**
960 	 * See
961 	 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}.
962 	 *
963 	 * @param base
964 	 *            The path against which <code>other</code> should be
965 	 *            relativized.
966 	 * @param other
967 	 *            The path that will be made relative to <code>base</code>.
968 	 * @return A relative path that, when resolved against <code>base</code>,
969 	 *         will yield the original <code>other</code>.
970 	 * @see FileUtils#relativizePath(String, String, String, boolean)
971 	 * @since 3.7
972 	 */
973 	public String relativize(String base, String other) {
974 		return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
975 	}
976 
977 	/**
978 	 * Enumerates children of a directory.
979 	 *
980 	 * @param directory
981 	 *            to get the children of
982 	 * @param fileModeStrategy
983 	 *            to use to calculate the git mode of a child
984 	 * @return an array of entries for the children
985 	 *
986 	 * @since 5.0
987 	 */
988 	public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
989 		final File[] all = directory.listFiles();
990 		if (all == null) {
991 			return NO_ENTRIES;
992 		}
993 		final Entry[] result = new Entry[all.length];
994 		for (int i = 0; i < result.length; i++) {
995 			result[i] = new FileEntry(all[i], this, fileModeStrategy);
996 		}
997 		return result;
998 	}
999 
1000 	/**
1001 	 * Checks whether the given hook is defined for the given repository, then
1002 	 * runs it with the given arguments.
1003 	 * <p>
1004 	 * The hook's standard output and error streams will be redirected to
1005 	 * <code>System.out</code> and <code>System.err</code> respectively. The
1006 	 * hook will have no stdin.
1007 	 * </p>
1008 	 *
1009 	 * @param repository
1010 	 *            The repository for which a hook should be run.
1011 	 * @param hookName
1012 	 *            The name of the hook to be executed.
1013 	 * @param args
1014 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1015 	 *            but can be an empty array.
1016 	 * @return The ProcessResult describing this hook's execution.
1017 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1018 	 *             if we fail to run the hook somehow. Causes may include an
1019 	 *             interrupted process or I/O errors.
1020 	 * @since 4.0
1021 	 */
1022 	public ProcessResult runHookIfPresent(Repository repository,
1023 			final String hookName,
1024 			String[] args) throws JGitInternalException {
1025 		return runHookIfPresent(repository, hookName, args, System.out, System.err,
1026 				null);
1027 	}
1028 
1029 	/**
1030 	 * Checks whether the given hook is defined for the given repository, then
1031 	 * runs it with the given arguments.
1032 	 *
1033 	 * @param repository
1034 	 *            The repository for which a hook should be run.
1035 	 * @param hookName
1036 	 *            The name of the hook to be executed.
1037 	 * @param args
1038 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1039 	 *            but can be an empty array.
1040 	 * @param outRedirect
1041 	 *            A print stream on which to redirect the hook's stdout. Can be
1042 	 *            <code>null</code>, in which case the hook's standard output
1043 	 *            will be lost.
1044 	 * @param errRedirect
1045 	 *            A print stream on which to redirect the hook's stderr. Can be
1046 	 *            <code>null</code>, in which case the hook's standard error
1047 	 *            will be lost.
1048 	 * @param stdinArgs
1049 	 *            A string to pass on to the standard input of the hook. May be
1050 	 *            <code>null</code>.
1051 	 * @return The ProcessResult describing this hook's execution.
1052 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1053 	 *             if we fail to run the hook somehow. Causes may include an
1054 	 *             interrupted process or I/O errors.
1055 	 * @since 4.0
1056 	 */
1057 	public ProcessResult runHookIfPresent(Repository repository,
1058 			final String hookName,
1059 			String[] args, PrintStream outRedirect, PrintStream errRedirect,
1060 			String stdinArgs) throws JGitInternalException {
1061 		return new ProcessResult(Status.NOT_SUPPORTED);
1062 	}
1063 
1064 	/**
1065 	 * See
1066 	 * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)}
1067 	 * . Should only be called by FS supporting shell scripts execution.
1068 	 *
1069 	 * @param repository
1070 	 *            The repository for which a hook should be run.
1071 	 * @param hookName
1072 	 *            The name of the hook to be executed.
1073 	 * @param args
1074 	 *            Arguments to pass to this hook. Cannot be <code>null</code>,
1075 	 *            but can be an empty array.
1076 	 * @param outRedirect
1077 	 *            A print stream on which to redirect the hook's stdout. Can be
1078 	 *            <code>null</code>, in which case the hook's standard output
1079 	 *            will be lost.
1080 	 * @param errRedirect
1081 	 *            A print stream on which to redirect the hook's stderr. Can be
1082 	 *            <code>null</code>, in which case the hook's standard error
1083 	 *            will be lost.
1084 	 * @param stdinArgs
1085 	 *            A string to pass on to the standard input of the hook. May be
1086 	 *            <code>null</code>.
1087 	 * @return The ProcessResult describing this hook's execution.
1088 	 * @throws org.eclipse.jgit.api.errors.JGitInternalException
1089 	 *             if we fail to run the hook somehow. Causes may include an
1090 	 *             interrupted process or I/O errors.
1091 	 * @since 4.0
1092 	 */
1093 	protected ProcessResult internalRunHookIfPresent(Repository repository,
1094 			final String hookName, String[] args, PrintStream outRedirect,
1095 			PrintStream errRedirect, String stdinArgs)
1096 			throws JGitInternalException {
1097 		final File hookFile = findHook(repository, hookName);
1098 		if (hookFile == null)
1099 			return new ProcessResult(Status.NOT_PRESENT);
1100 
1101 		final String hookPath = hookFile.getAbsolutePath();
1102 		final File runDirectory;
1103 		if (repository.isBare())
1104 			runDirectory = repository.getDirectory();
1105 		else
1106 			runDirectory = repository.getWorkTree();
1107 		final String cmd = relativize(runDirectory.getAbsolutePath(),
1108 				hookPath);
1109 		ProcessBuilder hookProcess = runInShell(cmd, args);
1110 		hookProcess.directory(runDirectory);
1111 		try {
1112 			return new ProcessResult(runProcess(hookProcess, outRedirect,
1113 					errRedirect, stdinArgs), Status.OK);
1114 		} catch (IOException e) {
1115 			throw new JGitInternalException(MessageFormat.format(
1116 					JGitText.get().exceptionCaughtDuringExecutionOfHook,
1117 					hookName), e);
1118 		} catch (InterruptedException e) {
1119 			throw new JGitInternalException(MessageFormat.format(
1120 					JGitText.get().exceptionHookExecutionInterrupted,
1121 							hookName), e);
1122 		}
1123 	}
1124 
1125 
1126 	/**
1127 	 * Tries to find a hook matching the given one in the given repository.
1128 	 *
1129 	 * @param repository
1130 	 *            The repository within which to find a hook.
1131 	 * @param hookName
1132 	 *            The name of the hook we're trying to find.
1133 	 * @return The {@link java.io.File} containing this particular hook if it
1134 	 *         exists in the given repository, <code>null</code> otherwise.
1135 	 * @since 4.0
1136 	 */
1137 	public File findHook(Repository repository, String hookName) {
1138 		File gitDir = repository.getDirectory();
1139 		if (gitDir == null)
1140 			return null;
1141 		final File hookFile = new File(new File(gitDir,
1142 				Constants.HOOKS), hookName);
1143 		return hookFile.isFile() ? hookFile : null;
1144 	}
1145 
1146 	/**
1147 	 * Runs the given process until termination, clearing its stdout and stderr
1148 	 * streams on-the-fly.
1149 	 *
1150 	 * @param processBuilder
1151 	 *            The process builder configured for this process.
1152 	 * @param outRedirect
1153 	 *            A OutputStream on which to redirect the processes stdout. Can
1154 	 *            be <code>null</code>, in which case the processes standard
1155 	 *            output will be lost.
1156 	 * @param errRedirect
1157 	 *            A OutputStream on which to redirect the processes stderr. Can
1158 	 *            be <code>null</code>, in which case the processes standard
1159 	 *            error will be lost.
1160 	 * @param stdinArgs
1161 	 *            A string to pass on to the standard input of the hook. Can be
1162 	 *            <code>null</code>.
1163 	 * @return the exit value of this process.
1164 	 * @throws java.io.IOException
1165 	 *             if an I/O error occurs while executing this process.
1166 	 * @throws java.lang.InterruptedException
1167 	 *             if the current thread is interrupted while waiting for the
1168 	 *             process to end.
1169 	 * @since 4.2
1170 	 */
1171 	public int runProcess(ProcessBuilder processBuilder,
1172 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1173 			throws IOException, InterruptedException {
1174 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1175 				stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
1176 		return runProcess(processBuilder, outRedirect, errRedirect, in);
1177 	}
1178 
1179 	/**
1180 	 * Runs the given process until termination, clearing its stdout and stderr
1181 	 * streams on-the-fly.
1182 	 *
1183 	 * @param processBuilder
1184 	 *            The process builder configured for this process.
1185 	 * @param outRedirect
1186 	 *            An OutputStream on which to redirect the processes stdout. Can
1187 	 *            be <code>null</code>, in which case the processes standard
1188 	 *            output will be lost.
1189 	 * @param errRedirect
1190 	 *            An OutputStream on which to redirect the processes stderr. Can
1191 	 *            be <code>null</code>, in which case the processes standard
1192 	 *            error will be lost.
1193 	 * @param inRedirect
1194 	 *            An InputStream from which to redirect the processes stdin. Can
1195 	 *            be <code>null</code>, in which case the process doesn't get
1196 	 *            any data over stdin. It is assumed that the whole InputStream
1197 	 *            will be consumed by the process. The method will close the
1198 	 *            inputstream after all bytes are read.
1199 	 * @return the return code of this process.
1200 	 * @throws java.io.IOException
1201 	 *             if an I/O error occurs while executing this process.
1202 	 * @throws java.lang.InterruptedException
1203 	 *             if the current thread is interrupted while waiting for the
1204 	 *             process to end.
1205 	 * @since 4.2
1206 	 */
1207 	public int runProcess(ProcessBuilder processBuilder,
1208 			OutputStream outRedirect, OutputStream errRedirect,
1209 			InputStream inRedirect) throws IOException,
1210 			InterruptedException {
1211 		final ExecutorService executor = Executors.newFixedThreadPool(2);
1212 		Process process = null;
1213 		// We'll record the first I/O exception that occurs, but keep on trying
1214 		// to dispose of our open streams and file handles
1215 		IOException ioException = null;
1216 		try {
1217 			process = processBuilder.start();
1218 			executor.execute(
1219 					new StreamGobbler(process.getErrorStream(), errRedirect));
1220 			executor.execute(
1221 					new StreamGobbler(process.getInputStream(), outRedirect));
1222 			@SuppressWarnings("resource") // Closed in the finally block
1223 			OutputStream outputStream = process.getOutputStream();
1224 			try {
1225 				if (inRedirect != null) {
1226 					new StreamGobbler(inRedirect, outputStream).copy();
1227 				}
1228 			} finally {
1229 				try {
1230 					outputStream.close();
1231 				} catch (IOException e) {
1232 					// When the process exits before consuming the input, the OutputStream
1233 					// is replaced with the null output stream. This null output stream
1234 					// throws IOException for all write calls. When StreamGobbler fails to
1235 					// flush the buffer because of this, this close call tries to flush it
1236 					// again. This causes another IOException. Since we ignore the
1237 					// IOException in StreamGobbler, we also ignore the exception here.
1238 				}
1239 			}
1240 			return process.waitFor();
1241 		} catch (IOException e) {
1242 			ioException = e;
1243 		} finally {
1244 			shutdownAndAwaitTermination(executor);
1245 			if (process != null) {
1246 				try {
1247 					process.waitFor();
1248 				} catch (InterruptedException e) {
1249 					// Thrown by the outer try.
1250 					// Swallow this one to carry on our cleanup, and clear the
1251 					// interrupted flag (processes throw the exception without
1252 					// clearing the flag).
1253 					Thread.interrupted();
1254 				}
1255 				// A process doesn't clean its own resources even when destroyed
1256 				// Explicitly try and close all three streams, preserving the
1257 				// outer I/O exception if any.
1258 				if (inRedirect != null) {
1259 					inRedirect.close();
1260 				}
1261 				try {
1262 					process.getErrorStream().close();
1263 				} catch (IOException e) {
1264 					ioException = ioException != null ? ioException : e;
1265 				}
1266 				try {
1267 					process.getInputStream().close();
1268 				} catch (IOException e) {
1269 					ioException = ioException != null ? ioException : e;
1270 				}
1271 				try {
1272 					process.getOutputStream().close();
1273 				} catch (IOException e) {
1274 					ioException = ioException != null ? ioException : e;
1275 				}
1276 				process.destroy();
1277 			}
1278 		}
1279 		// We can only be here if the outer try threw an IOException.
1280 		throw ioException;
1281 	}
1282 
1283 	/**
1284 	 * Shuts down an {@link ExecutorService} in two phases, first by calling
1285 	 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
1286 	 * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
1287 	 * necessary, to cancel any lingering tasks. Returns true if the pool has
1288 	 * been properly shutdown, false otherwise.
1289 	 * <p>
1290 	 *
1291 	 * @param pool
1292 	 *            the pool to shutdown
1293 	 * @return <code>true</code> if the pool has been properly shutdown,
1294 	 *         <code>false</code> otherwise.
1295 	 */
1296 	private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1297 		boolean hasShutdown = true;
1298 		pool.shutdown(); // Disable new tasks from being submitted
1299 		try {
1300 			// Wait a while for existing tasks to terminate
1301 			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1302 				pool.shutdownNow(); // Cancel currently executing tasks
1303 				// Wait a while for tasks to respond to being canceled
1304 				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1305 					hasShutdown = false;
1306 			}
1307 		} catch (InterruptedException ie) {
1308 			// (Re-)Cancel if current thread also interrupted
1309 			pool.shutdownNow();
1310 			// Preserve interrupt status
1311 			Thread.currentThread().interrupt();
1312 			hasShutdown = false;
1313 		}
1314 		return hasShutdown;
1315 	}
1316 
1317 	/**
1318 	 * Initialize a ProcessBuilder to run a command using the system shell.
1319 	 *
1320 	 * @param cmd
1321 	 *            command to execute. This string should originate from the
1322 	 *            end-user, and thus is platform specific.
1323 	 * @param args
1324 	 *            arguments to pass to command. These should be protected from
1325 	 *            shell evaluation.
1326 	 * @return a partially completed process builder. Caller should finish
1327 	 *         populating directory, environment, and then start the process.
1328 	 */
1329 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
1330 
1331 	/**
1332 	 * Execute a command defined by a {@link java.lang.ProcessBuilder}.
1333 	 *
1334 	 * @param pb
1335 	 *            The command to be executed
1336 	 * @param in
1337 	 *            The standard input stream passed to the process
1338 	 * @return The result of the executed command
1339 	 * @throws java.lang.InterruptedException
1340 	 * @throws java.io.IOException
1341 	 * @since 4.2
1342 	 */
1343 	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1344 			throws IOException, InterruptedException {
1345 		try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1346 				TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1347 						1024 * 1024)) {
1348 			int rc = runProcess(pb, stdout, stderr, in);
1349 			return new ExecutionResult(stdout, stderr, rc);
1350 		}
1351 	}
1352 
1353 	private static class Holder<V> {
1354 		final V value;
1355 
1356 		Holder(V value) {
1357 			this.value = value;
1358 		}
1359 	}
1360 
1361 	/**
1362 	 * File attributes we typically care for.
1363 	 *
1364 	 * @since 3.3
1365 	 */
1366 	public static class Attributes {
1367 
1368 		/**
1369 		 * @return true if this are the attributes of a directory
1370 		 */
1371 		public boolean isDirectory() {
1372 			return isDirectory;
1373 		}
1374 
1375 		/**
1376 		 * @return true if this are the attributes of an executable file
1377 		 */
1378 		public boolean isExecutable() {
1379 			return isExecutable;
1380 		}
1381 
1382 		/**
1383 		 * @return true if this are the attributes of a symbolic link
1384 		 */
1385 		public boolean isSymbolicLink() {
1386 			return isSymbolicLink;
1387 		}
1388 
1389 		/**
1390 		 * @return true if this are the attributes of a regular file
1391 		 */
1392 		public boolean isRegularFile() {
1393 			return isRegularFile;
1394 		}
1395 
1396 		/**
1397 		 * @return the time when the file was created
1398 		 */
1399 		public long getCreationTime() {
1400 			return creationTime;
1401 		}
1402 
1403 		/**
1404 		 * @return the time (milliseconds since 1970-01-01) when this object was
1405 		 *         last modified
1406 		 */
1407 		public long getLastModifiedTime() {
1408 			return lastModifiedTime;
1409 		}
1410 
1411 		private final boolean isDirectory;
1412 
1413 		private final boolean isSymbolicLink;
1414 
1415 		private final boolean isRegularFile;
1416 
1417 		private final long creationTime;
1418 
1419 		private final long lastModifiedTime;
1420 
1421 		private final boolean isExecutable;
1422 
1423 		private final File file;
1424 
1425 		private final boolean exists;
1426 
1427 		/**
1428 		 * file length
1429 		 */
1430 		protected long length = -1;
1431 
1432 		final FS fs;
1433 
1434 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1435 				boolean isExecutable, boolean isSymbolicLink,
1436 				boolean isRegularFile, long creationTime,
1437 				long lastModifiedTime, long length) {
1438 			this.fs = fs;
1439 			this.file = file;
1440 			this.exists = exists;
1441 			this.isDirectory = isDirectory;
1442 			this.isExecutable = isExecutable;
1443 			this.isSymbolicLink = isSymbolicLink;
1444 			this.isRegularFile = isRegularFile;
1445 			this.creationTime = creationTime;
1446 			this.lastModifiedTime = lastModifiedTime;
1447 			this.length = length;
1448 		}
1449 
1450 		/**
1451 		 * Constructor when there are issues with reading. All attributes except
1452 		 * given will be set to the default values.
1453 		 *
1454 		 * @param fs
1455 		 * @param path
1456 		 */
1457 		public Attributes(File path, FS fs) {
1458 			this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1459 		}
1460 
1461 		/**
1462 		 * @return length of this file object
1463 		 */
1464 		public long getLength() {
1465 			if (length == -1)
1466 				return length = file.length();
1467 			return length;
1468 		}
1469 
1470 		/**
1471 		 * @return the filename
1472 		 */
1473 		public String getName() {
1474 			return file.getName();
1475 		}
1476 
1477 		/**
1478 		 * @return the file the attributes apply to
1479 		 */
1480 		public File getFile() {
1481 			return file;
1482 		}
1483 
1484 		boolean exists() {
1485 			return exists;
1486 		}
1487 	}
1488 
1489 	/**
1490 	 * Get the file attributes we care for.
1491 	 *
1492 	 * @param path
1493 	 *            a {@link java.io.File} object.
1494 	 * @return the file attributes we care for.
1495 	 * @since 3.3
1496 	 */
1497 	public Attributes getAttributes(File path) {
1498 		boolean isDirectory = isDirectory(path);
1499 		boolean isFile = !isDirectory && path.isFile();
1500 		assert path.exists() == isDirectory || isFile;
1501 		boolean exists = isDirectory || isFile;
1502 		boolean canExecute = exists && !isDirectory && canExecute(path);
1503 		boolean isSymlink = false;
1504 		long lastModified = exists ? path.lastModified() : 0L;
1505 		long createTime = 0L;
1506 		return new Attributes(this, path, exists, isDirectory, canExecute,
1507 				isSymlink, isFile, createTime, lastModified, -1);
1508 	}
1509 
1510 	/**
1511 	 * Normalize the unicode path to composed form.
1512 	 *
1513 	 * @param file
1514 	 *            a {@link java.io.File} object.
1515 	 * @return NFC-format File
1516 	 * @since 3.3
1517 	 */
1518 	public File normalize(File file) {
1519 		return file;
1520 	}
1521 
1522 	/**
1523 	 * Normalize the unicode path to composed form.
1524 	 *
1525 	 * @param name
1526 	 *            path name
1527 	 * @return NFC-format string
1528 	 * @since 3.3
1529 	 */
1530 	public String normalize(String name) {
1531 		return name;
1532 	}
1533 
1534 	/**
1535 	 * This runnable will consume an input stream's content into an output
1536 	 * stream as soon as it gets available.
1537 	 * <p>
1538 	 * Typically used to empty processes' standard output and error, preventing
1539 	 * them to choke.
1540 	 * </p>
1541 	 * <p>
1542 	 * <b>Note</b> that a {@link StreamGobbler} will never close either of its
1543 	 * streams.
1544 	 * </p>
1545 	 */
1546 	private static class StreamGobbler implements Runnable {
1547 		private InputStream in;
1548 
1549 		private OutputStream out;
1550 
1551 		public StreamGobbler(InputStream stream, OutputStream output) {
1552 			this.in = stream;
1553 			this.out = output;
1554 		}
1555 
1556 		@Override
1557 		public void run() {
1558 			try {
1559 				copy();
1560 			} catch (IOException e) {
1561 				// Do nothing on read failure; leave streams open.
1562 			}
1563 		}
1564 
1565 		void copy() throws IOException {
1566 			boolean writeFailure = false;
1567 			byte buffer[] = new byte[4096];
1568 			int readBytes;
1569 			while ((readBytes = in.read(buffer)) != -1) {
1570 				// Do not try to write again after a failure, but keep
1571 				// reading as long as possible to prevent the input stream
1572 				// from choking.
1573 				if (!writeFailure && out != null) {
1574 					try {
1575 						out.write(buffer, 0, readBytes);
1576 						out.flush();
1577 					} catch (IOException e) {
1578 						writeFailure = true;
1579 					}
1580 				}
1581 			}
1582 		}
1583 	}
1584 }