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