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