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