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