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