1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.util;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47 import static java.time.Instant.EPOCH;
48
49 import java.io.BufferedReader;
50 import java.io.ByteArrayInputStream;
51 import java.io.Closeable;
52 import java.io.File;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.InputStreamReader;
56 import java.io.OutputStream;
57 import java.io.OutputStreamWriter;
58 import java.io.PrintStream;
59 import java.io.Writer;
60 import java.nio.charset.Charset;
61 import java.nio.file.AccessDeniedException;
62 import java.nio.file.FileStore;
63 import java.nio.file.Files;
64 import java.nio.file.Path;
65 import java.nio.file.attribute.BasicFileAttributes;
66 import java.nio.file.attribute.FileTime;
67 import java.security.AccessControlException;
68 import java.security.AccessController;
69 import java.security.PrivilegedAction;
70 import java.text.MessageFormat;
71 import java.time.Duration;
72 import java.time.Instant;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.HashMap;
76 import java.util.Map;
77 import java.util.Objects;
78 import java.util.Optional;
79 import java.util.UUID;
80 import java.util.concurrent.CancellationException;
81 import java.util.concurrent.CompletableFuture;
82 import java.util.concurrent.ConcurrentHashMap;
83 import java.util.concurrent.ExecutionException;
84 import java.util.concurrent.ExecutorService;
85 import java.util.concurrent.Executors;
86 import java.util.concurrent.TimeUnit;
87 import java.util.concurrent.TimeoutException;
88 import java.util.concurrent.atomic.AtomicBoolean;
89 import java.util.concurrent.atomic.AtomicReference;
90 import java.util.concurrent.locks.Lock;
91 import java.util.concurrent.locks.ReentrantLock;
92
93 import org.eclipse.jgit.annotations.NonNull;
94 import org.eclipse.jgit.annotations.Nullable;
95 import org.eclipse.jgit.api.errors.JGitInternalException;
96 import org.eclipse.jgit.errors.CommandFailedException;
97 import org.eclipse.jgit.errors.ConfigInvalidException;
98 import org.eclipse.jgit.errors.LockFailedException;
99 import org.eclipse.jgit.internal.JGitText;
100 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
101 import org.eclipse.jgit.lib.ConfigConstants;
102 import org.eclipse.jgit.lib.Constants;
103 import org.eclipse.jgit.lib.Repository;
104 import org.eclipse.jgit.lib.StoredConfig;
105 import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
106 import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
107 import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
108 import org.eclipse.jgit.util.ProcessResult.Status;
109 import org.slf4j.Logger;
110 import org.slf4j.LoggerFactory;
111
112
113
114
115 public abstract class FS {
116 private static final Logger LOG = LoggerFactory.getLogger(FS.class);
117
118
119
120
121
122
123
124 protected static final Entry[] NO_ENTRIES = {};
125
126 private volatile Boolean supportSymlinks;
127
128
129
130
131
132
133
134 public static class FSFactory {
135
136
137
138 protected FSFactory() {
139
140 }
141
142
143
144
145
146
147
148 public FS detect(Boolean cygwinUsed) {
149 if (SystemReader.getInstance().isWindows()) {
150 if (cygwinUsed == null)
151 cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
152 if (cygwinUsed.booleanValue())
153 return new FS_Win32_Cygwin();
154 else
155 return new FS_Win32();
156 } else {
157 return new FS_POSIX();
158 }
159 }
160 }
161
162
163
164
165
166
167
168 public static class ExecutionResult {
169 private TemporaryBuffer stdout;
170
171 private TemporaryBuffer stderr;
172
173 private int rc;
174
175
176
177
178
179
180 public ExecutionResult(TemporaryBuffer./../org/eclipse/jgit/util/TemporaryBuffer.html#TemporaryBuffer">TemporaryBuffer stdout, TemporaryBuffer stderr,
181 int rc) {
182 this.stdout = stdout;
183 this.stderr = stderr;
184 this.rc = rc;
185 }
186
187
188
189
190 public TemporaryBuffer getStdout() {
191 return stdout;
192 }
193
194
195
196
197 public TemporaryBuffer getStderr() {
198 return stderr;
199 }
200
201
202
203
204 public int getRc() {
205 return rc;
206 }
207 }
208
209
210
211
212
213
214 public final static class FileStoreAttributes {
215
216 private static final Duration UNDEFINED_DURATION = Duration
217 .ofNanos(Long.MAX_VALUE);
218
219
220
221
222
223 public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
224 .ofMillis(2000);
225
226
227
228
229
230
231 public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
232 FALLBACK_TIMESTAMP_RESOLUTION);
233
234 private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
235
236 private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
237 100, 0.2f);
238
239 private static AtomicBoolean background = new AtomicBoolean();
240
241 private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
242
243 private static void setBackground(boolean async) {
244 background.set(async);
245 }
246
247 private static final String javaVersionPrefix = System
248 .getProperty("java.vendor") + '|'
249 + System.getProperty("java.version") + '|';
250
251 private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
252 .ofMillis(10);
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269 public static void configureAttributesPathCache(int maxSize,
270 float purgeFactor) {
271 FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
272 }
273
274
275
276
277
278
279
280
281 public static FileStoreAttributes get(Path path) {
282 try {
283 path = path.toAbsolutePath();
284 Path dir = Files.isDirectory(path) ? path : path.getParent();
285 FileStoreAttributes cached = attrCacheByPath.get(dir);
286 if (cached != null) {
287 return cached;
288 }
289 FileStoreAttributes attrs = getFileStoreAttributes(dir);
290 attrCacheByPath.put(dir, attrs);
291 return attrs;
292 } catch (SecurityException e) {
293 return FALLBACK_FILESTORE_ATTRIBUTES;
294 }
295 }
296
297 private static FileStoreAttributes getFileStoreAttributes(Path dir) {
298 FileStore s;
299 try {
300 if (Files.exists(dir)) {
301 s = Files.getFileStore(dir);
302 FileStoreAttributes c = attributeCache.get(s);
303 if (c != null) {
304 return c;
305 }
306 if (!Files.isWritable(dir)) {
307
308 LOG.debug(
309 "{}: cannot measure timestamp resolution in read-only directory {}",
310 Thread.currentThread(), dir);
311 return FALLBACK_FILESTORE_ATTRIBUTES;
312 }
313 } else {
314
315 LOG.debug(
316 "{}: cannot measure timestamp resolution of unborn directory {}",
317 Thread.currentThread(), dir);
318 return FALLBACK_FILESTORE_ATTRIBUTES;
319 }
320
321 CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
322 .supplyAsync(() -> {
323 Lock lock = locks.computeIfAbsent(s,
324 l -> new ReentrantLock());
325 if (!lock.tryLock()) {
326 LOG.debug(
327 "{}: couldn't get lock to measure timestamp resolution in {}",
328 Thread.currentThread(), dir);
329 return Optional.empty();
330 }
331 Optional<FileStoreAttributes> attributes = Optional
332 .empty();
333 try {
334
335
336
337 FileStoreAttributes c = attributeCache
338 .get(s);
339 if (c != null) {
340 return Optional.of(c);
341 }
342 attributes = readFromConfig(s);
343 if (attributes.isPresent()) {
344 attributeCache.put(s, attributes.get());
345 return attributes;
346 }
347
348 Optional<Duration> resolution = measureFsTimestampResolution(
349 s, dir);
350 if (resolution.isPresent()) {
351 c = new FileStoreAttributes(
352 resolution.get());
353 attributeCache.put(s, c);
354
355
356 if (c.fsTimestampResolution
357 .toNanos() < 100_000_000L) {
358 c.minimalRacyInterval = measureMinimalRacyInterval(
359 dir);
360 }
361 if (LOG.isDebugEnabled()) {
362 LOG.debug(c.toString());
363 }
364 saveToConfig(s, c);
365 }
366 attributes = Optional.of(c);
367 } finally {
368 lock.unlock();
369 locks.remove(s);
370 }
371 return attributes;
372 });
373 f = f.exceptionally(e -> {
374 LOG.error(e.getLocalizedMessage(), e);
375 return Optional.empty();
376 });
377
378
379 Optional<FileStoreAttributes> d = background.get() ? f.get(
380 100, TimeUnit.MILLISECONDS) : f.get();
381 if (d.isPresent()) {
382 return d.get();
383 }
384
385 } catch (IOException | InterruptedException
386 | ExecutionException | CancellationException e) {
387 LOG.error(e.getMessage(), e);
388 } catch (TimeoutException | SecurityException e) {
389
390 }
391 LOG.debug("{}: use fallback timestamp resolution for directory {}",
392 Thread.currentThread(), dir);
393 return FALLBACK_FILESTORE_ATTRIBUTES;
394 }
395
396 @SuppressWarnings("boxing")
397 private static Duration measureMinimalRacyInterval(Path dir) {
398 LOG.debug("{}: start measure minimal racy interval in {}",
399 Thread.currentThread(), dir);
400 int n = 0;
401 int failures = 0;
402 long racyNanos = 0;
403 ArrayList<Long> deltas = new ArrayList<>();
404 Path probe = dir.resolve(".probe-" + UUID.randomUUID());
405 Instant end = Instant.now().plusSeconds(3);
406 try {
407 Files.createFile(probe);
408 do {
409 n++;
410 write(probe, "a");
411 FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
412 read(probe);
413 write(probe, "b");
414 if (!snapshot.isModified(probe.toFile())) {
415 deltas.add(Long.valueOf(snapshot.lastDelta()));
416 racyNanos = snapshot.lastRacyThreshold();
417 failures++;
418 }
419 } while (Instant.now().compareTo(end) < 0);
420 } catch (IOException e) {
421 LOG.error(e.getMessage(), e);
422 return FALLBACK_MIN_RACY_INTERVAL;
423 } finally {
424 deleteProbe(probe);
425 }
426 if (failures > 0) {
427 Stats stats = new Stats();
428 for (Long d : deltas) {
429 stats.add(d);
430 }
431 LOG.debug(
432 "delta [ns] since modification FileSnapshot failed to detect\n"
433 + "count, failures, racy limit [ns], delta min [ns],"
434 + " delta max [ns], delta avg [ns],"
435 + " delta stddev [ns]\n"
436 + "{}, {}, {}, {}, {}, {}, {}",
437 n, failures, racyNanos, stats.min(), stats.max(),
438 stats.avg(), stats.stddev());
439 return Duration
440 .ofNanos(Double.valueOf(stats.max()).longValue());
441 }
442
443
444 LOG.debug("{}: no failures when measuring minimal racy interval",
445 Thread.currentThread());
446 return Duration.ZERO;
447 }
448
449 private static void write(Path p, String body) throws IOException {
450 FileUtils.mkdirs(p.getParent().toFile(), true);
451 try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
452 UTF_8)) {
453 w.write(body);
454 }
455 }
456
457 private static String read(Path p) throws IOException {
458 final byte[] body = IO.readFully(p.toFile());
459 return new String(body, 0, body.length, UTF_8);
460 }
461
462 private static Optional<Duration> measureFsTimestampResolution(
463 FileStore s, Path dir) {
464 LOG.debug("{}: start measure timestamp resolution {} in {}",
465 Thread.currentThread(), s, dir);
466 Path probe = dir.resolve(".probe-" + UUID.randomUUID());
467 try {
468 Files.createFile(probe);
469 FileTime t1 = Files.getLastModifiedTime(probe);
470 FileTime t2 = t1;
471 Instant t1i = t1.toInstant();
472 for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) {
473 Files.setLastModifiedTime(probe,
474 FileTime.from(t1i.plusNanos(i * 1000)));
475 t2 = Files.getLastModifiedTime(probe);
476 }
477 Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant());
478 Duration clockResolution = measureClockResolution();
479 fsResolution = fsResolution.plus(clockResolution);
480 LOG.debug("{}: end measure timestamp resolution {} in {}",
481 Thread.currentThread(), s, dir);
482 return Optional.of(fsResolution);
483 } catch (AccessDeniedException e) {
484 LOG.warn(e.getLocalizedMessage(), e);
485 } catch (IOException e) {
486 LOG.error(e.getLocalizedMessage(), e);
487 } finally {
488 deleteProbe(probe);
489 }
490 return Optional.empty();
491 }
492
493 private static Duration measureClockResolution() {
494 Duration clockResolution = Duration.ZERO;
495 for (int i = 0; i < 10; i++) {
496 Instant t1 = Instant.now();
497 Instant t2 = t1;
498 while (t2.compareTo(t1) <= 0) {
499 t2 = Instant.now();
500 }
501 Duration r = Duration.between(t1, t2);
502 if (r.compareTo(clockResolution) > 0) {
503 clockResolution = r;
504 }
505 }
506 return clockResolution;
507 }
508
509 private static void deleteProbe(Path probe) {
510 try {
511 FileUtils.delete(probe.toFile(),
512 FileUtils.SKIP_MISSING | FileUtils.RETRY);
513 } catch (IOException e) {
514 LOG.error(e.getMessage(), e);
515 }
516 }
517
518 private static Optional<FileStoreAttributes> readFromConfig(
519 FileStore s) {
520 StoredConfig userConfig;
521 try {
522 userConfig = SystemReader.getInstance().getUserConfig();
523 } catch (IOException | ConfigInvalidException e) {
524 LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
525 return Optional.empty();
526 }
527 String key = getConfigKey(s);
528 Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
529 ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
530 ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
531 UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
532 if (UNDEFINED_DURATION.equals(resolution)) {
533 return Optional.empty();
534 }
535 Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
536 ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
537 ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
538 UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
539 FileStoreAttributes c = new FileStoreAttributes(resolution);
540 if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
541 c.minimalRacyInterval = minRacyThreshold;
542 }
543 return Optional.of(c);
544 }
545
546 private static void saveToConfig(FileStore s,
547 FileStoreAttributes c) {
548 StoredConfig userConfig;
549 try {
550 userConfig = SystemReader.getInstance().getUserConfig();
551 } catch (IOException | ConfigInvalidException e) {
552 LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
553 return;
554 }
555 long resolution = c.getFsTimestampResolution().toNanos();
556 TimeUnit resolutionUnit = getUnit(resolution);
557 long resolutionValue = resolutionUnit.convert(resolution,
558 TimeUnit.NANOSECONDS);
559
560 long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
561 TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
562 long minRacyThresholdValue = minRacyThresholdUnit
563 .convert(minRacyThreshold, TimeUnit.NANOSECONDS);
564
565 final int max_retries = 5;
566 int retries = 0;
567 boolean succeeded = false;
568 String key = getConfigKey(s);
569 while (!succeeded && retries < max_retries) {
570 try {
571 userConfig.load();
572 userConfig.setString(
573 ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
574 ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
575 String.format("%d %s",
576 Long.valueOf(resolutionValue),
577 resolutionUnit.name().toLowerCase()));
578 userConfig.setString(
579 ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
580 ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
581 String.format("%d %s",
582 Long.valueOf(minRacyThresholdValue),
583 minRacyThresholdUnit.name().toLowerCase()));
584 userConfig.save();
585 succeeded = true;
586 } catch (LockFailedException e) {
587
588 try {
589 retries++;
590 if (retries < max_retries) {
591 Thread.sleep(100);
592 LOG.debug("locking {} failed, retries {}/{}",
593 userConfig, Integer.valueOf(retries),
594 Integer.valueOf(max_retries));
595 } else {
596 LOG.warn(MessageFormat.format(
597 JGitText.get().lockFailedRetry, userConfig,
598 Integer.valueOf(retries)));
599 }
600 } catch (InterruptedException e1) {
601 Thread.currentThread().interrupt();
602 break;
603 }
604 } catch (IOException e) {
605 LOG.error(MessageFormat.format(
606 JGitText.get().cannotSaveConfig, userConfig), e);
607 break;
608 } catch (ConfigInvalidException e) {
609 LOG.error(MessageFormat.format(
610 JGitText.get().repositoryConfigFileInvalid,
611 userConfig, e.getMessage()));
612 break;
613 }
614 }
615 }
616
617 private static String getConfigKey(FileStore s) {
618 final String storeKey;
619 if (SystemReader.getInstance().isWindows()) {
620 Object attribute = null;
621 try {
622 attribute = s.getAttribute("volume:vsn");
623 } catch (IOException ignored) {
624
625 }
626 if (attribute instanceof Integer) {
627 storeKey = attribute.toString();
628 } else {
629 storeKey = s.name();
630 }
631 } else {
632 storeKey = s.name();
633 }
634 return javaVersionPrefix + storeKey;
635 }
636
637 private static TimeUnit getUnit(long nanos) {
638 TimeUnit unit;
639 if (nanos < 200_000L) {
640 unit = TimeUnit.NANOSECONDS;
641 } else if (nanos < 200_000_000L) {
642 unit = TimeUnit.MICROSECONDS;
643 } else {
644 unit = TimeUnit.MILLISECONDS;
645 }
646 return unit;
647 }
648
649 private final @NonNull Duration fsTimestampResolution;
650
651 private Duration minimalRacyInterval;
652
653
654
655
656
657
658 public Duration getMinimalRacyInterval() {
659 return minimalRacyInterval;
660 }
661
662
663
664
665 @NonNull
666 public Duration getFsTimestampResolution() {
667 return fsTimestampResolution;
668 }
669
670
671
672
673
674
675
676 public FileStoreAttributes(
677 @NonNull Duration fsTimestampResolution) {
678 this.fsTimestampResolution = fsTimestampResolution;
679 this.minimalRacyInterval = Duration.ZERO;
680 }
681
682 @SuppressWarnings({ "nls", "boxing" })
683 @Override
684 public String toString() {
685 return String.format(
686 "FileStoreAttributes[fsTimestampResolution=%,d µs, "
687 + "minimalRacyInterval=%,d µs]",
688 fsTimestampResolution.toNanos() / 1000,
689 minimalRacyInterval.toNanos() / 1000);
690 }
691
692 }
693
694
695 public static final FS DETECTED = detect();
696
697 private volatile static FSFactory factory;
698
699
700
701
702
703
704 public static FS detect() {
705 return detect(null);
706 }
707
708
709
710
711
712
713
714
715
716
717 public static void setAsyncFileStoreAttributes(boolean asynch) {
718 FileStoreAttributes.setBackground(asynch);
719 }
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741 public static FS detect(Boolean cygwinUsed) {
742 if (factory == null) {
743 factory = new FS.FSFactory();
744 }
745 return factory.detect(cygwinUsed);
746 }
747
748
749
750
751
752
753
754
755
756
757
758 public static FileStoreAttributes getFileStoreAttributes(
759 @NonNull Path dir) {
760 return FileStoreAttributes.get(dir);
761 }
762
763 private volatile Holder<File> userHome;
764
765 private volatile Holder<File> gitSystemConfig;
766
767
768
769
770 protected FS() {
771
772 }
773
774
775
776
777
778
779
780 protected FSme="FS" href="../../../../org/eclipse/jgit/util/FS.html#FS">FS(FS src) {
781 userHome = src.userHome;
782 gitSystemConfig = src.gitSystemConfig;
783 }
784
785
786
787
788
789
790 public abstract FS newInstance();
791
792
793
794
795
796
797
798 public abstract boolean supportsExecute();
799
800
801
802
803
804
805
806
807
808
809
810
811 public boolean supportsAtomicCreateNewFile() {
812 return true;
813 }
814
815
816
817
818
819
820
821
822 public boolean supportsSymlinks() {
823 if (supportSymlinks == null) {
824 detectSymlinkSupport();
825 }
826 return Boolean.TRUE.equals(supportSymlinks);
827 }
828
829 private void detectSymlinkSupport() {
830 File tempFile = null;
831 try {
832 tempFile = File.createTempFile("tempsymlinktarget", "");
833 File linkName = new File(tempFile.getParentFile(), "tempsymlink");
834 createSymLink(linkName, tempFile.getPath());
835 supportSymlinks = Boolean.TRUE;
836 linkName.delete();
837 } catch (IOException | UnsupportedOperationException | SecurityException
838 | InternalError e) {
839 supportSymlinks = Boolean.FALSE;
840 } finally {
841 if (tempFile != null) {
842 try {
843 FileUtils.delete(tempFile);
844 } catch (IOException e) {
845 throw new RuntimeException(e);
846 }
847 }
848 }
849 }
850
851
852
853
854
855
856 public abstract boolean isCaseSensitive();
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872 public abstract boolean canExecute(File f);
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887 public abstract boolean setExecute(File f, boolean canExec);
888
889
890
891
892
893
894
895
896
897
898
899
900
901 @Deprecated
902 public long lastModified(File f) throws IOException {
903 return FileUtils.lastModified(f);
904 }
905
906
907
908
909
910
911
912
913
914
915
916 public Instant lastModifiedInstant(Path p) {
917 return FileUtils.lastModifiedInstant(p);
918 }
919
920
921
922
923
924
925
926
927
928
929
930 public Instant lastModifiedInstant(File f) {
931 return FileUtils.lastModifiedInstant(f.toPath());
932 }
933
934
935
936
937
938
939
940
941
942
943
944
945
946 @Deprecated
947 public void setLastModified(File f, long time) throws IOException {
948 FileUtils.setLastModified(f, time);
949 }
950
951
952
953
954
955
956
957
958
959
960
961
962 public void setLastModified(Path p, Instant time) throws IOException {
963 FileUtils.setLastModified(p, time);
964 }
965
966
967
968
969
970
971
972
973
974
975
976 public long length(File path) throws IOException {
977 return FileUtils.getLength(path);
978 }
979
980
981
982
983
984
985
986
987
988
989 public void delete(File f) throws IOException {
990 FileUtils.delete(f);
991 }
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011 public File resolve(File dir, String name) {
1012 final File abspn = new File(name);
1013 if (abspn.isAbsolute())
1014 return abspn;
1015 return new File(dir, name);
1016 }
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029 public File userHome() {
1030 Holder<File> p = userHome;
1031 if (p == null) {
1032 p = new Holder<>(userHomeImpl());
1033 userHome = p;
1034 }
1035 return p.value;
1036 }
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046 public FS setUserHome(File path) {
1047 userHome = new Holder<>(path);
1048 return this;
1049 }
1050
1051
1052
1053
1054
1055
1056 public abstract boolean retryFailedLockFileCommit();
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067 public BasicFileAttributes fileAttributes(File file) throws IOException {
1068 return FileUtils.fileAttributes(file);
1069 }
1070
1071
1072
1073
1074
1075
1076 protected File userHomeImpl() {
1077 final String home = AccessController.doPrivileged(
1078 (PrivilegedAction<String>) () -> System.getProperty("user.home")
1079 );
1080 if (home == null || home.length() == 0)
1081 return null;
1082 return new File(home).getAbsoluteFile();
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 protected static File searchPath(String path, String... lookFor) {
1097 if (path == null)
1098 return null;
1099
1100 for (String p : path.split(File.pathSeparator)) {
1101 for (String command : lookFor) {
1102 final File file = new File(p, command);
1103 try {
1104 if (file.isFile()) {
1105 return file.getAbsoluteFile();
1106 }
1107 } catch (SecurityException e) {
1108 LOG.warn(MessageFormat.format(
1109 JGitText.get().skipNotAccessiblePath,
1110 file.getPath()));
1111 }
1112 }
1113 }
1114 return null;
1115 }
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131 @Nullable
1132 protected static String readPipe(File dir, String[] command,
1133 String encoding) throws CommandFailedException {
1134 return readPipe(dir, command, encoding, null);
1135 }
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155 @Nullable
1156 protected static String readPipe(File dir, String[] command,
1157 String encoding, Map<String, String> env)
1158 throws CommandFailedException {
1159 final boolean debug = LOG.isDebugEnabled();
1160 try {
1161 if (debug) {
1162 LOG.debug("readpipe " + Arrays.asList(command) + ","
1163 + dir);
1164 }
1165 ProcessBuilder pb = new ProcessBuilder(command);
1166 pb.directory(dir);
1167 if (env != null) {
1168 pb.environment().putAll(env);
1169 }
1170 Process p;
1171 try {
1172 p = pb.start();
1173 } catch (IOException e) {
1174
1175 throw new CommandFailedException(-1, e.getMessage(), e);
1176 }
1177 p.getOutputStream().close();
1178 GobblerThread gobbler = new GobblerThread(p, command, dir);
1179 gobbler.start();
1180 String r = null;
1181 try (BufferedReader lineRead = new BufferedReader(
1182 new InputStreamReader(p.getInputStream(), encoding))) {
1183 r = lineRead.readLine();
1184 if (debug) {
1185 LOG.debug("readpipe may return '" + r + "'");
1186 LOG.debug("remaining output:\n");
1187 String l;
1188 while ((l = lineRead.readLine()) != null) {
1189 LOG.debug(l);
1190 }
1191 }
1192 }
1193
1194 for (;;) {
1195 try {
1196 int rc = p.waitFor();
1197 gobbler.join();
1198 if (rc == 0 && !gobbler.fail.get()) {
1199 return r;
1200 } else {
1201 if (debug) {
1202 LOG.debug("readpipe rc=" + rc);
1203 }
1204 throw new CommandFailedException(rc,
1205 gobbler.errorMessage.get(),
1206 gobbler.exception.get());
1207 }
1208 } catch (InterruptedException ie) {
1209
1210 }
1211 }
1212 } catch (IOException e) {
1213 LOG.error("Caught exception in FS.readPipe()", e);
1214 } catch (AccessControlException e) {
1215 LOG.warn(MessageFormat.format(
1216 JGitText.get().readPipeIsNotAllowedRequiredPermission,
1217 command, dir, e.getPermission()));
1218 } catch (SecurityException e) {
1219 LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
1220 command, dir));
1221 }
1222 if (debug) {
1223 LOG.debug("readpipe returns null");
1224 }
1225 return null;
1226 }
1227
1228 private static class GobblerThread extends Thread {
1229
1230
1231 private static final int PROCESS_EXIT_TIMEOUT = 5;
1232
1233 private final Process p;
1234 private final String desc;
1235 private final String dir;
1236 final AtomicBoolean fail = new AtomicBoolean();
1237 final AtomicReference<String> errorMessage = new AtomicReference<>();
1238 final AtomicReference<Throwable> exception = new AtomicReference<>();
1239
1240 GobblerThread(Process p, String[] command, File dir) {
1241 this.p = p;
1242 this.desc = Arrays.toString(command);
1243 this.dir = Objects.toString(dir);
1244 }
1245
1246 @Override
1247 public void run() {
1248 StringBuilder err = new StringBuilder();
1249 try (InputStream is = p.getErrorStream()) {
1250 int ch;
1251 while ((ch = is.read()) != -1) {
1252 err.append((char) ch);
1253 }
1254 } catch (IOException e) {
1255 if (waitForProcessCompletion(e) && p.exitValue() != 0) {
1256 setError(e, e.getMessage(), p.exitValue());
1257 fail.set(true);
1258 } else {
1259
1260
1261 }
1262 } finally {
1263 if (waitForProcessCompletion(null) && err.length() > 0) {
1264 setError(null, err.toString(), p.exitValue());
1265 if (p.exitValue() != 0) {
1266 fail.set(true);
1267 }
1268 }
1269 }
1270 }
1271
1272 @SuppressWarnings("boxing")
1273 private boolean waitForProcessCompletion(IOException originalError) {
1274 try {
1275 if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
1276 setError(originalError, MessageFormat.format(
1277 JGitText.get().commandClosedStderrButDidntExit,
1278 desc, PROCESS_EXIT_TIMEOUT), -1);
1279 fail.set(true);
1280 return false;
1281 }
1282 } catch (InterruptedException e) {
1283 setError(originalError, MessageFormat.format(
1284 JGitText.get().threadInterruptedWhileRunning, desc), -1);
1285 fail.set(true);
1286 return false;
1287 }
1288 return true;
1289 }
1290
1291 private void setError(IOException e, String message, int exitCode) {
1292 exception.set(e);
1293 errorMessage.set(MessageFormat.format(
1294 JGitText.get().exceptionCaughtDuringExecutionOfCommand,
1295 desc, dir, Integer.valueOf(exitCode), message));
1296 }
1297 }
1298
1299
1300
1301
1302
1303
1304
1305
1306 protected abstract File discoverGitExe();
1307
1308
1309
1310
1311
1312
1313
1314
1315 protected File discoverGitSystemConfig() {
1316 File gitExe = discoverGitExe();
1317 if (gitExe == null) {
1318 return null;
1319 }
1320
1321
1322 String v;
1323 try {
1324 v = readPipe(gitExe.getParentFile(),
1325 new String[] { "git", "--version" },
1326 Charset.defaultCharset().name());
1327 } catch (CommandFailedException e) {
1328 LOG.warn(e.getMessage());
1329 return null;
1330 }
1331 if (StringUtils.isEmptyOrNull(v)
1332 || (v != null && v.startsWith("jgit"))) {
1333 return null;
1334 }
1335
1336
1337
1338 Map<String, String> env = new HashMap<>();
1339 env.put("GIT_EDITOR", "echo");
1340
1341 String w;
1342 try {
1343 w = readPipe(gitExe.getParentFile(),
1344 new String[] { "git", "config", "--system", "--edit" },
1345 Charset.defaultCharset().name(), env);
1346 } catch (CommandFailedException e) {
1347 LOG.warn(e.getMessage());
1348 return null;
1349 }
1350 if (StringUtils.isEmptyOrNull(w)) {
1351 return null;
1352 }
1353
1354 return new File(w);
1355 }
1356
1357
1358
1359
1360
1361
1362
1363
1364 public File getGitSystemConfig() {
1365 if (gitSystemConfig == null) {
1366 gitSystemConfig = new Holder<>(discoverGitSystemConfig());
1367 }
1368 return gitSystemConfig.value;
1369 }
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379 public FS setGitSystemConfig(File configFile) {
1380 gitSystemConfig = new Holder<>(configFile);
1381 return this;
1382 }
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393 protected static File resolveGrandparentFile(File grandchild) {
1394 if (grandchild != null) {
1395 File parent = grandchild.getParentFile();
1396 if (parent != null)
1397 return parent.getParentFile();
1398 }
1399 return null;
1400 }
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411 public String readSymLink(File path) throws IOException {
1412 return FileUtils.readSymLink(path);
1413 }
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424 public boolean isSymLink(File path) throws IOException {
1425 return FileUtils.isSymlink(path);
1426 }
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437 public boolean exists(File path) {
1438 return FileUtils.exists(path);
1439 }
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450 public boolean isDirectory(File path) {
1451 return FileUtils.isDirectory(path);
1452 }
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463 public boolean isFile(File path) {
1464 return FileUtils.isFile(path);
1465 }
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478 public boolean isHidden(File path) throws IOException {
1479 return FileUtils.isHidden(path);
1480 }
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492 public void setHidden(File path, boolean hidden) throws IOException {
1493 FileUtils.setHidden(path, hidden);
1494 }
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506 public void createSymLink(File path, String target) throws IOException {
1507 FileUtils.createSymLink(path, target);
1508 }
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523 @Deprecated
1524 public boolean createNewFile(File path) throws IOException {
1525 return path.createNewFile();
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537 public static class LockToken implements Closeable {
1538 private boolean isCreated;
1539
1540 private Optional<Path> link;
1541
1542 LockToken(boolean isCreated, Optional<Path> link) {
1543 this.isCreated = isCreated;
1544 this.link = link;
1545 }
1546
1547
1548
1549
1550 public boolean isCreated() {
1551 return isCreated;
1552 }
1553
1554 @Override
1555 public void close() {
1556 if (!link.isPresent()) {
1557 return;
1558 }
1559 Path p = link.get();
1560 if (!Files.exists(p)) {
1561 return;
1562 }
1563 try {
1564 Files.delete(p);
1565 } catch (IOException e) {
1566 LOG.error(MessageFormat
1567 .format(JGitText.get().closeLockTokenFailed, this), e);
1568 }
1569 }
1570
1571 @Override
1572 public String toString() {
1573 return "LockToken [lockCreated=" + isCreated +
1574 ", link="
1575 + (link.isPresent() ? link.get().getFileName() + "]"
1576 : "<null>]");
1577 }
1578 }
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592 public LockToken createNewFileAtomic(File path) throws IOException {
1593 return new LockToken(path.createNewFile(), Optional.empty());
1594 }
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610 public String relativize(String base, String other) {
1611 return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
1612 }
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625 public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1626 final File[] all = directory.listFiles();
1627 if (all == null) {
1628 return NO_ENTRIES;
1629 }
1630 final Entry[] result = new Entry[all.length];
1631 for (int i = 0; i < result.length; i++) {
1632 result[i] = new FileEntry(all[i], this, fileModeStrategy);
1633 }
1634 return result;
1635 }
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659 public ProcessResult runHookIfPresent(Repository repository,
1660 final String hookName,
1661 String[] args) throws JGitInternalException {
1662 return runHookIfPresent(repository, hookName, args, System.out, System.err,
1663 null);
1664 }
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694 public ProcessResult runHookIfPresent(Repository repository,
1695 final String hookName,
1696 String[] args, PrintStream outRedirect, PrintStream errRedirect,
1697 String stdinArgs) throws JGitInternalException {
1698 return new ProcessResult(Status.NOT_SUPPORTED);
1699 }
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730 protected ProcessResult internalRunHookIfPresent(Repository repository,
1731 final String hookName, String[] args, PrintStream outRedirect,
1732 PrintStream errRedirect, String stdinArgs)
1733 throws JGitInternalException {
1734 final File hookFile = findHook(repository, hookName);
1735 if (hookFile == null)
1736 return new ProcessResult(Status.NOT_PRESENT);
1737
1738 final String hookPath = hookFile.getAbsolutePath();
1739 final File runDirectory;
1740 if (repository.isBare())
1741 runDirectory = repository.getDirectory();
1742 else
1743 runDirectory = repository.getWorkTree();
1744 final String cmd = relativize(runDirectory.getAbsolutePath(),
1745 hookPath);
1746 ProcessBuilder hookProcess = runInShell(cmd, args);
1747 hookProcess.directory(runDirectory);
1748 Map<String, String> environment = hookProcess.environment();
1749 environment.put(Constants.GIT_DIR_KEY,
1750 repository.getDirectory().getAbsolutePath());
1751 if (!repository.isBare()) {
1752 environment.put(Constants.GIT_WORK_TREE_KEY,
1753 repository.getWorkTree().getAbsolutePath());
1754 }
1755 try {
1756 return new ProcessResult(runProcess(hookProcess, outRedirect,
1757 errRedirect, stdinArgs), Status.OK);
1758 } catch (IOException e) {
1759 throw new JGitInternalException(MessageFormat.format(
1760 JGitText.get().exceptionCaughtDuringExecutionOfHook,
1761 hookName), e);
1762 } catch (InterruptedException e) {
1763 throw new JGitInternalException(MessageFormat.format(
1764 JGitText.get().exceptionHookExecutionInterrupted,
1765 hookName), e);
1766 }
1767 }
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781 public File findHook(Repository repository, String hookName) {
1782 File gitDir = repository.getDirectory();
1783 if (gitDir == null)
1784 return null;
1785 final File hookFile = new File(new File(gitDir,
1786 Constants.HOOKS), hookName);
1787 return hookFile.isFile() ? hookFile : null;
1788 }
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815 public int runProcess(ProcessBuilder processBuilder,
1816 OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1817 throws IOException, InterruptedException {
1818 InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1819 stdinArgs.getBytes(UTF_8));
1820 return runProcess(processBuilder, outRedirect, errRedirect, in);
1821 }
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851 public int runProcess(ProcessBuilder processBuilder,
1852 OutputStream outRedirect, OutputStream errRedirect,
1853 InputStream inRedirect) throws IOException,
1854 InterruptedException {
1855 final ExecutorService executor = Executors.newFixedThreadPool(2);
1856 Process process = null;
1857
1858
1859 IOException ioException = null;
1860 try {
1861 process = processBuilder.start();
1862 executor.execute(
1863 new StreamGobbler(process.getErrorStream(), errRedirect));
1864 executor.execute(
1865 new StreamGobbler(process.getInputStream(), outRedirect));
1866 @SuppressWarnings("resource")
1867 OutputStream outputStream = process.getOutputStream();
1868 try {
1869 if (inRedirect != null) {
1870 new StreamGobbler(inRedirect, outputStream).copy();
1871 }
1872 } finally {
1873 try {
1874 outputStream.close();
1875 } catch (IOException e) {
1876
1877
1878
1879
1880
1881
1882 }
1883 }
1884 return process.waitFor();
1885 } catch (IOException e) {
1886 ioException = e;
1887 } finally {
1888 shutdownAndAwaitTermination(executor);
1889 if (process != null) {
1890 try {
1891 process.waitFor();
1892 } catch (InterruptedException e) {
1893
1894
1895
1896
1897 Thread.interrupted();
1898 }
1899
1900
1901
1902 if (inRedirect != null) {
1903 inRedirect.close();
1904 }
1905 try {
1906 process.getErrorStream().close();
1907 } catch (IOException e) {
1908 ioException = ioException != null ? ioException : e;
1909 }
1910 try {
1911 process.getInputStream().close();
1912 } catch (IOException e) {
1913 ioException = ioException != null ? ioException : e;
1914 }
1915 try {
1916 process.getOutputStream().close();
1917 } catch (IOException e) {
1918 ioException = ioException != null ? ioException : e;
1919 }
1920 process.destroy();
1921 }
1922 }
1923
1924 throw ioException;
1925 }
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1941 boolean hasShutdown = true;
1942 pool.shutdown();
1943 try {
1944
1945 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1946 pool.shutdownNow();
1947
1948 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1949 hasShutdown = false;
1950 }
1951 } catch (InterruptedException ie) {
1952
1953 pool.shutdownNow();
1954
1955 Thread.currentThread().interrupt();
1956 hasShutdown = false;
1957 }
1958 return hasShutdown;
1959 }
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973 public abstract ProcessBuilder runInShell(String cmd, String[] args);
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987 public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1988 throws IOException, InterruptedException {
1989 try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1990 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1991 1024 * 1024)) {
1992 int rc = runProcess(pb, stdout, stderr, in);
1993 return new ExecutionResult(stdout, stderr, rc);
1994 }
1995 }
1996
1997 private static class Holder<V> {
1998 final V value;
1999
2000 Holder(V value) {
2001 this.value = value;
2002 }
2003 }
2004
2005
2006
2007
2008
2009
2010 public static class Attributes {
2011
2012
2013
2014
2015 public boolean isDirectory() {
2016 return isDirectory;
2017 }
2018
2019
2020
2021
2022 public boolean isExecutable() {
2023 return isExecutable;
2024 }
2025
2026
2027
2028
2029 public boolean isSymbolicLink() {
2030 return isSymbolicLink;
2031 }
2032
2033
2034
2035
2036 public boolean isRegularFile() {
2037 return isRegularFile;
2038 }
2039
2040
2041
2042
2043 public long getCreationTime() {
2044 return creationTime;
2045 }
2046
2047
2048
2049
2050
2051
2052 @Deprecated
2053 public long getLastModifiedTime() {
2054 return lastModifiedInstant.toEpochMilli();
2055 }
2056
2057
2058
2059
2060
2061 public Instant getLastModifiedInstant() {
2062 return lastModifiedInstant;
2063 }
2064
2065 private final boolean isDirectory;
2066
2067 private final boolean isSymbolicLink;
2068
2069 private final boolean isRegularFile;
2070
2071 private final long creationTime;
2072
2073 private final Instant lastModifiedInstant;
2074
2075 private final boolean isExecutable;
2076
2077 private final File file;
2078
2079 private final boolean exists;
2080
2081
2082
2083
2084 protected long length = -1;
2085
2086 final FS fs;
2087
2088 Attributes(FS fs, File file, boolean exists, boolean isDirectory,
2089 boolean isExecutable, boolean isSymbolicLink,
2090 boolean isRegularFile, long creationTime,
2091 Instant lastModifiedInstant, long length) {
2092 this.fs = fs;
2093 this.file = file;
2094 this.exists = exists;
2095 this.isDirectory = isDirectory;
2096 this.isExecutable = isExecutable;
2097 this.isSymbolicLink = isSymbolicLink;
2098 this.isRegularFile = isRegularFile;
2099 this.creationTime = creationTime;
2100 this.lastModifiedInstant = lastModifiedInstant;
2101 this.length = length;
2102 }
2103
2104
2105
2106
2107
2108
2109
2110
2111 public Attributes(File path, FS fs) {
2112 this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
2113 }
2114
2115
2116
2117
2118 public long getLength() {
2119 if (length == -1)
2120 return length = file.length();
2121 return length;
2122 }
2123
2124
2125
2126
2127 public String getName() {
2128 return file.getName();
2129 }
2130
2131
2132
2133
2134 public File getFile() {
2135 return file;
2136 }
2137
2138 boolean exists() {
2139 return exists;
2140 }
2141 }
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151 public Attributes getAttributes(File path) {
2152 boolean isDirectory = isDirectory(path);
2153 boolean isFile = !isDirectory && path.isFile();
2154 assert path.exists() == isDirectory || isFile;
2155 boolean exists = isDirectory || isFile;
2156 boolean canExecute = exists && !isDirectory && canExecute(path);
2157 boolean isSymlink = false;
2158 Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
2159 long createTime = 0L;
2160 return new Attributes(this, path, exists, isDirectory, canExecute,
2161 isSymlink, isFile, createTime, lastModified, -1);
2162 }
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172 public File normalize(File file) {
2173 return file;
2174 }
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184 public String normalize(String name) {
2185 return name;
2186 }
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200 private static class StreamGobbler implements Runnable {
2201 private InputStream in;
2202
2203 private OutputStream out;
2204
2205 public StreamGobbler(InputStream stream, OutputStream output) {
2206 this.in = stream;
2207 this.out = output;
2208 }
2209
2210 @Override
2211 public void run() {
2212 try {
2213 copy();
2214 } catch (IOException e) {
2215
2216 }
2217 }
2218
2219 void copy() throws IOException {
2220 boolean writeFailure = false;
2221 byte buffer[] = new byte[4096];
2222 int readBytes;
2223 while ((readBytes = in.read(buffer)) != -1) {
2224
2225
2226
2227 if (!writeFailure && out != null) {
2228 try {
2229 out.write(buffer, 0, readBytes);
2230 out.flush();
2231 } catch (IOException e) {
2232 writeFailure = true;
2233 }
2234 }
2235 }
2236 }
2237 }
2238 }