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