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