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