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.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
1046 .doPrivileged(new PrivilegedAction<String>() {
1047 @Override
1048 public String run() {
1049 return System.getProperty("user.home");
1050 }
1051 });
1052 if (home == null || home.length() == 0)
1053 return null;
1054 return new File(home).getAbsoluteFile();
1055 }
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068 protected static File searchPath(String path, String... lookFor) {
1069 if (path == null)
1070 return null;
1071
1072 for (String p : path.split(File.pathSeparator)) {
1073 for (String command : lookFor) {
1074 final File e = new File(p, command);
1075 if (e.isFile())
1076 return e.getAbsoluteFile();
1077 }
1078 }
1079 return null;
1080 }
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 @Nullable
1097 protected static String readPipe(File dir, String[] command,
1098 String encoding) throws CommandFailedException {
1099 return readPipe(dir, command, encoding, null);
1100 }
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120 @Nullable
1121 protected static String readPipe(File dir, String[] command,
1122 String encoding, Map<String, String> env)
1123 throws CommandFailedException {
1124 final boolean debug = LOG.isDebugEnabled();
1125 try {
1126 if (debug) {
1127 LOG.debug("readpipe " + Arrays.asList(command) + ","
1128 + dir);
1129 }
1130 ProcessBuilder pb = new ProcessBuilder(command);
1131 pb.directory(dir);
1132 if (env != null) {
1133 pb.environment().putAll(env);
1134 }
1135 Process p;
1136 try {
1137 p = pb.start();
1138 } catch (IOException e) {
1139
1140 throw new CommandFailedException(-1, e.getMessage(), e);
1141 }
1142 p.getOutputStream().close();
1143 GobblerThread gobbler = new GobblerThread(p, command, dir);
1144 gobbler.start();
1145 String r = null;
1146 try (BufferedReader lineRead = new BufferedReader(
1147 new InputStreamReader(p.getInputStream(), encoding))) {
1148 r = lineRead.readLine();
1149 if (debug) {
1150 LOG.debug("readpipe may return '" + r + "'");
1151 LOG.debug("remaining output:\n");
1152 String l;
1153 while ((l = lineRead.readLine()) != null) {
1154 LOG.debug(l);
1155 }
1156 }
1157 }
1158
1159 for (;;) {
1160 try {
1161 int rc = p.waitFor();
1162 gobbler.join();
1163 if (rc == 0 && !gobbler.fail.get()) {
1164 return r;
1165 } else {
1166 if (debug) {
1167 LOG.debug("readpipe rc=" + rc);
1168 }
1169 throw new CommandFailedException(rc,
1170 gobbler.errorMessage.get(),
1171 gobbler.exception.get());
1172 }
1173 } catch (InterruptedException ie) {
1174
1175 }
1176 }
1177 } catch (IOException e) {
1178 LOG.error("Caught exception in FS.readPipe()", e);
1179 }
1180 if (debug) {
1181 LOG.debug("readpipe returns null");
1182 }
1183 return null;
1184 }
1185
1186 private static class GobblerThread extends Thread {
1187
1188
1189 private static final int PROCESS_EXIT_TIMEOUT = 5;
1190
1191 private final Process p;
1192 private final String desc;
1193 private final String dir;
1194 final AtomicBoolean fail = new AtomicBoolean();
1195 final AtomicReference<String> errorMessage = new AtomicReference<>();
1196 final AtomicReference<Throwable> exception = new AtomicReference<>();
1197
1198 GobblerThread(Process p, String[] command, File dir) {
1199 this.p = p;
1200 this.desc = Arrays.toString(command);
1201 this.dir = Objects.toString(dir);
1202 }
1203
1204 @Override
1205 public void run() {
1206 StringBuilder err = new StringBuilder();
1207 try (InputStream is = p.getErrorStream()) {
1208 int ch;
1209 while ((ch = is.read()) != -1) {
1210 err.append((char) ch);
1211 }
1212 } catch (IOException e) {
1213 if (waitForProcessCompletion(e) && p.exitValue() != 0) {
1214 setError(e, e.getMessage(), p.exitValue());
1215 fail.set(true);
1216 } else {
1217
1218
1219 }
1220 } finally {
1221 if (waitForProcessCompletion(null) && err.length() > 0) {
1222 setError(null, err.toString(), p.exitValue());
1223 if (p.exitValue() != 0) {
1224 fail.set(true);
1225 }
1226 }
1227 }
1228 }
1229
1230 @SuppressWarnings("boxing")
1231 private boolean waitForProcessCompletion(IOException originalError) {
1232 try {
1233 if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
1234 setError(originalError, MessageFormat.format(
1235 JGitText.get().commandClosedStderrButDidntExit,
1236 desc, PROCESS_EXIT_TIMEOUT), -1);
1237 fail.set(true);
1238 return false;
1239 }
1240 } catch (InterruptedException e) {
1241 setError(originalError, MessageFormat.format(
1242 JGitText.get().threadInterruptedWhileRunning, desc), -1);
1243 fail.set(true);
1244 return false;
1245 }
1246 return true;
1247 }
1248
1249 private void setError(IOException e, String message, int exitCode) {
1250 exception.set(e);
1251 errorMessage.set(MessageFormat.format(
1252 JGitText.get().exceptionCaughtDuringExecutionOfCommand,
1253 desc, dir, Integer.valueOf(exitCode), message));
1254 }
1255 }
1256
1257
1258
1259
1260
1261
1262
1263
1264 protected abstract File discoverGitExe();
1265
1266
1267
1268
1269
1270
1271
1272
1273 protected File discoverGitSystemConfig() {
1274 File gitExe = discoverGitExe();
1275 if (gitExe == null) {
1276 return null;
1277 }
1278
1279
1280 String v;
1281 try {
1282 v = readPipe(gitExe.getParentFile(),
1283 new String[] { "git", "--version" },
1284 Charset.defaultCharset().name());
1285 } catch (CommandFailedException e) {
1286 LOG.warn(e.getMessage());
1287 return null;
1288 }
1289 if (StringUtils.isEmptyOrNull(v)
1290 || (v != null && v.startsWith("jgit"))) {
1291 return null;
1292 }
1293
1294
1295
1296 Map<String, String> env = new HashMap<>();
1297 env.put("GIT_EDITOR", "echo");
1298
1299 String w;
1300 try {
1301 w = readPipe(gitExe.getParentFile(),
1302 new String[] { "git", "config", "--system", "--edit" },
1303 Charset.defaultCharset().name(), env);
1304 } catch (CommandFailedException e) {
1305 LOG.warn(e.getMessage());
1306 return null;
1307 }
1308 if (StringUtils.isEmptyOrNull(w)) {
1309 return null;
1310 }
1311
1312 return new File(w);
1313 }
1314
1315
1316
1317
1318
1319
1320
1321
1322 public File getGitSystemConfig() {
1323 if (gitSystemConfig == null) {
1324 gitSystemConfig = new Holder<>(discoverGitSystemConfig());
1325 }
1326 return gitSystemConfig.value;
1327 }
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337 public FS setGitSystemConfig(File configFile) {
1338 gitSystemConfig = new Holder<>(configFile);
1339 return this;
1340 }
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351 protected static File resolveGrandparentFile(File grandchild) {
1352 if (grandchild != null) {
1353 File parent = grandchild.getParentFile();
1354 if (parent != null)
1355 return parent.getParentFile();
1356 }
1357 return null;
1358 }
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369 public String readSymLink(File path) throws IOException {
1370 return FileUtils.readSymLink(path);
1371 }
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382 public boolean isSymLink(File path) throws IOException {
1383 return FileUtils.isSymlink(path);
1384 }
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395 public boolean exists(File path) {
1396 return FileUtils.exists(path);
1397 }
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408 public boolean isDirectory(File path) {
1409 return FileUtils.isDirectory(path);
1410 }
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421 public boolean isFile(File path) {
1422 return FileUtils.isFile(path);
1423 }
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436 public boolean isHidden(File path) throws IOException {
1437 return FileUtils.isHidden(path);
1438 }
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450 public void setHidden(File path, boolean hidden) throws IOException {
1451 FileUtils.setHidden(path, hidden);
1452 }
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464 public void createSymLink(File path, String target) throws IOException {
1465 FileUtils.createSymLink(path, target);
1466 }
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481 @Deprecated
1482 public boolean createNewFile(File path) throws IOException {
1483 return path.createNewFile();
1484 }
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495 public static class LockToken implements Closeable {
1496 private boolean isCreated;
1497
1498 private Optional<Path> link;
1499
1500 LockToken(boolean isCreated, Optional<Path> link) {
1501 this.isCreated = isCreated;
1502 this.link = link;
1503 }
1504
1505
1506
1507
1508 public boolean isCreated() {
1509 return isCreated;
1510 }
1511
1512 @Override
1513 public void close() {
1514 if (!link.isPresent()) {
1515 return;
1516 }
1517 Path p = link.get();
1518 if (!Files.exists(p)) {
1519 return;
1520 }
1521 try {
1522 Files.delete(p);
1523 } catch (IOException e) {
1524 LOG.error(MessageFormat
1525 .format(JGitText.get().closeLockTokenFailed, this), e);
1526 }
1527 }
1528
1529 @Override
1530 public String toString() {
1531 return "LockToken [lockCreated=" + isCreated +
1532 ", link="
1533 + (link.isPresent() ? link.get().getFileName() + "]"
1534 : "<null>]");
1535 }
1536 }
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550 public LockToken createNewFileAtomic(File path) throws IOException {
1551 return new LockToken(path.createNewFile(), Optional.empty());
1552 }
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568 public String relativize(String base, String other) {
1569 return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
1570 }
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583 public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1584 final File[] all = directory.listFiles();
1585 if (all == null) {
1586 return NO_ENTRIES;
1587 }
1588 final Entry[] result = new Entry[all.length];
1589 for (int i = 0; i < result.length; i++) {
1590 result[i] = new FileEntry(all[i], this, fileModeStrategy);
1591 }
1592 return result;
1593 }
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617 public ProcessResult runHookIfPresent(Repository repository,
1618 final String hookName,
1619 String[] args) throws JGitInternalException {
1620 return runHookIfPresent(repository, hookName, args, System.out, System.err,
1621 null);
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
1649
1650
1651
1652 public ProcessResult runHookIfPresent(Repository repository,
1653 final String hookName,
1654 String[] args, PrintStream outRedirect, PrintStream errRedirect,
1655 String stdinArgs) throws JGitInternalException {
1656 return new ProcessResult(Status.NOT_SUPPORTED);
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
1685
1686
1687
1688 protected ProcessResult internalRunHookIfPresent(Repository repository,
1689 final String hookName, String[] args, PrintStream outRedirect,
1690 PrintStream errRedirect, String stdinArgs)
1691 throws JGitInternalException {
1692 final File hookFile = findHook(repository, hookName);
1693 if (hookFile == null)
1694 return new ProcessResult(Status.NOT_PRESENT);
1695
1696 final String hookPath = hookFile.getAbsolutePath();
1697 final File runDirectory;
1698 if (repository.isBare())
1699 runDirectory = repository.getDirectory();
1700 else
1701 runDirectory = repository.getWorkTree();
1702 final String cmd = relativize(runDirectory.getAbsolutePath(),
1703 hookPath);
1704 ProcessBuilder hookProcess = runInShell(cmd, args);
1705 hookProcess.directory(runDirectory);
1706 Map<String, String> environment = hookProcess.environment();
1707 environment.put(Constants.GIT_DIR_KEY,
1708 repository.getDirectory().getAbsolutePath());
1709 if (!repository.isBare()) {
1710 environment.put(Constants.GIT_WORK_TREE_KEY,
1711 repository.getWorkTree().getAbsolutePath());
1712 }
1713 try {
1714 return new ProcessResult(runProcess(hookProcess, outRedirect,
1715 errRedirect, stdinArgs), Status.OK);
1716 } catch (IOException e) {
1717 throw new JGitInternalException(MessageFormat.format(
1718 JGitText.get().exceptionCaughtDuringExecutionOfHook,
1719 hookName), e);
1720 } catch (InterruptedException e) {
1721 throw new JGitInternalException(MessageFormat.format(
1722 JGitText.get().exceptionHookExecutionInterrupted,
1723 hookName), e);
1724 }
1725 }
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739 public File findHook(Repository repository, String hookName) {
1740 File gitDir = repository.getDirectory();
1741 if (gitDir == null)
1742 return null;
1743 final File hookFile = new File(new File(gitDir,
1744 Constants.HOOKS), hookName);
1745 return hookFile.isFile() ? hookFile : null;
1746 }
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773 public int runProcess(ProcessBuilder processBuilder,
1774 OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1775 throws IOException, InterruptedException {
1776 InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1777 stdinArgs.getBytes(UTF_8));
1778 return runProcess(processBuilder, outRedirect, errRedirect, in);
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
1806
1807
1808
1809 public int runProcess(ProcessBuilder processBuilder,
1810 OutputStream outRedirect, OutputStream errRedirect,
1811 InputStream inRedirect) throws IOException,
1812 InterruptedException {
1813 final ExecutorService executor = Executors.newFixedThreadPool(2);
1814 Process process = null;
1815
1816
1817 IOException ioException = null;
1818 try {
1819 process = processBuilder.start();
1820 executor.execute(
1821 new StreamGobbler(process.getErrorStream(), errRedirect));
1822 executor.execute(
1823 new StreamGobbler(process.getInputStream(), outRedirect));
1824 @SuppressWarnings("resource")
1825 OutputStream outputStream = process.getOutputStream();
1826 try {
1827 if (inRedirect != null) {
1828 new StreamGobbler(inRedirect, outputStream).copy();
1829 }
1830 } finally {
1831 try {
1832 outputStream.close();
1833 } catch (IOException e) {
1834
1835
1836
1837
1838
1839
1840 }
1841 }
1842 return process.waitFor();
1843 } catch (IOException e) {
1844 ioException = e;
1845 } finally {
1846 shutdownAndAwaitTermination(executor);
1847 if (process != null) {
1848 try {
1849 process.waitFor();
1850 } catch (InterruptedException e) {
1851
1852
1853
1854
1855 Thread.interrupted();
1856 }
1857
1858
1859
1860 if (inRedirect != null) {
1861 inRedirect.close();
1862 }
1863 try {
1864 process.getErrorStream().close();
1865 } catch (IOException e) {
1866 ioException = ioException != null ? ioException : e;
1867 }
1868 try {
1869 process.getInputStream().close();
1870 } catch (IOException e) {
1871 ioException = ioException != null ? ioException : e;
1872 }
1873 try {
1874 process.getOutputStream().close();
1875 } catch (IOException e) {
1876 ioException = ioException != null ? ioException : e;
1877 }
1878 process.destroy();
1879 }
1880 }
1881
1882 throw ioException;
1883 }
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1899 boolean hasShutdown = true;
1900 pool.shutdown();
1901 try {
1902
1903 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1904 pool.shutdownNow();
1905
1906 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1907 hasShutdown = false;
1908 }
1909 } catch (InterruptedException ie) {
1910
1911 pool.shutdownNow();
1912
1913 Thread.currentThread().interrupt();
1914 hasShutdown = false;
1915 }
1916 return hasShutdown;
1917 }
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931 public abstract ProcessBuilder runInShell(String cmd, String[] args);
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945 public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1946 throws IOException, InterruptedException {
1947 try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1948 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1949 1024 * 1024)) {
1950 int rc = runProcess(pb, stdout, stderr, in);
1951 return new ExecutionResult(stdout, stderr, rc);
1952 }
1953 }
1954
1955 private static class Holder<V> {
1956 final V value;
1957
1958 Holder(V value) {
1959 this.value = value;
1960 }
1961 }
1962
1963
1964
1965
1966
1967
1968 public static class Attributes {
1969
1970
1971
1972
1973 public boolean isDirectory() {
1974 return isDirectory;
1975 }
1976
1977
1978
1979
1980 public boolean isExecutable() {
1981 return isExecutable;
1982 }
1983
1984
1985
1986
1987 public boolean isSymbolicLink() {
1988 return isSymbolicLink;
1989 }
1990
1991
1992
1993
1994 public boolean isRegularFile() {
1995 return isRegularFile;
1996 }
1997
1998
1999
2000
2001 public long getCreationTime() {
2002 return creationTime;
2003 }
2004
2005
2006
2007
2008
2009
2010 @Deprecated
2011 public long getLastModifiedTime() {
2012 return lastModifiedInstant.toEpochMilli();
2013 }
2014
2015
2016
2017
2018
2019 public Instant getLastModifiedInstant() {
2020 return lastModifiedInstant;
2021 }
2022
2023 private final boolean isDirectory;
2024
2025 private final boolean isSymbolicLink;
2026
2027 private final boolean isRegularFile;
2028
2029 private final long creationTime;
2030
2031 private final Instant lastModifiedInstant;
2032
2033 private final boolean isExecutable;
2034
2035 private final File file;
2036
2037 private final boolean exists;
2038
2039
2040
2041
2042 protected long length = -1;
2043
2044 final FS fs;
2045
2046 Attributes(FS fs, File file, boolean exists, boolean isDirectory,
2047 boolean isExecutable, boolean isSymbolicLink,
2048 boolean isRegularFile, long creationTime,
2049 Instant lastModifiedInstant, long length) {
2050 this.fs = fs;
2051 this.file = file;
2052 this.exists = exists;
2053 this.isDirectory = isDirectory;
2054 this.isExecutable = isExecutable;
2055 this.isSymbolicLink = isSymbolicLink;
2056 this.isRegularFile = isRegularFile;
2057 this.creationTime = creationTime;
2058 this.lastModifiedInstant = lastModifiedInstant;
2059 this.length = length;
2060 }
2061
2062
2063
2064
2065
2066
2067
2068
2069 public Attributes(File path, FS fs) {
2070 this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
2071 }
2072
2073
2074
2075
2076 public long getLength() {
2077 if (length == -1)
2078 return length = file.length();
2079 return length;
2080 }
2081
2082
2083
2084
2085 public String getName() {
2086 return file.getName();
2087 }
2088
2089
2090
2091
2092 public File getFile() {
2093 return file;
2094 }
2095
2096 boolean exists() {
2097 return exists;
2098 }
2099 }
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109 public Attributes getAttributes(File path) {
2110 boolean isDirectory = isDirectory(path);
2111 boolean isFile = !isDirectory && path.isFile();
2112 assert path.exists() == isDirectory || isFile;
2113 boolean exists = isDirectory || isFile;
2114 boolean canExecute = exists && !isDirectory && canExecute(path);
2115 boolean isSymlink = false;
2116 Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
2117 long createTime = 0L;
2118 return new Attributes(this, path, exists, isDirectory, canExecute,
2119 isSymlink, isFile, createTime, lastModified, -1);
2120 }
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130 public File normalize(File file) {
2131 return file;
2132 }
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142 public String normalize(String name) {
2143 return name;
2144 }
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158 private static class StreamGobbler implements Runnable {
2159 private InputStream in;
2160
2161 private OutputStream out;
2162
2163 public StreamGobbler(InputStream stream, OutputStream output) {
2164 this.in = stream;
2165 this.out = output;
2166 }
2167
2168 @Override
2169 public void run() {
2170 try {
2171 copy();
2172 } catch (IOException e) {
2173
2174 }
2175 }
2176
2177 void copy() throws IOException {
2178 boolean writeFailure = false;
2179 byte buffer[] = new byte[4096];
2180 int readBytes;
2181 while ((readBytes = in.read(buffer)) != -1) {
2182
2183
2184
2185 if (!writeFailure && out != null) {
2186 try {
2187 out.write(buffer, 0, readBytes);
2188 out.flush();
2189 } catch (IOException e) {
2190 writeFailure = true;
2191 }
2192 }
2193 }
2194 }
2195 }
2196 }