1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.util;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47
48 import java.io.BufferedReader;
49 import java.io.ByteArrayInputStream;
50 import java.io.Closeable;
51 import java.io.File;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.InputStreamReader;
55 import java.io.OutputStream;
56 import java.io.PrintStream;
57 import java.nio.charset.Charset;
58 import java.nio.file.AccessDeniedException;
59 import java.nio.file.FileStore;
60 import java.nio.file.Files;
61 import java.nio.file.Path;
62 import java.nio.file.attribute.BasicFileAttributes;
63 import java.nio.file.attribute.FileTime;
64 import java.security.AccessController;
65 import java.security.PrivilegedAction;
66 import java.text.MessageFormat;
67 import java.time.Duration;
68 import java.util.Arrays;
69 import java.util.HashMap;
70 import java.util.Map;
71 import java.util.Objects;
72 import java.util.Optional;
73 import java.util.UUID;
74 import java.util.concurrent.ConcurrentHashMap;
75 import java.util.concurrent.ExecutorService;
76 import java.util.concurrent.Executors;
77 import java.util.concurrent.TimeUnit;
78 import java.util.concurrent.atomic.AtomicBoolean;
79 import java.util.concurrent.atomic.AtomicReference;
80 import java.util.stream.Collectors;
81
82 import org.eclipse.jgit.annotations.NonNull;
83 import org.eclipse.jgit.annotations.Nullable;
84 import org.eclipse.jgit.api.errors.JGitInternalException;
85 import org.eclipse.jgit.errors.CommandFailedException;
86 import org.eclipse.jgit.internal.JGitText;
87 import org.eclipse.jgit.lib.Constants;
88 import org.eclipse.jgit.lib.Repository;
89 import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
90 import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
91 import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
92 import org.eclipse.jgit.util.ProcessResult.Status;
93 import org.slf4j.Logger;
94 import org.slf4j.LoggerFactory;
95
96
97
98
99 public abstract class FS {
100 private static final Logger LOG = LoggerFactory.getLogger(FS.class);
101
102
103
104
105
106
107
108 protected static final Entry[] NO_ENTRIES = {};
109
110
111
112
113
114
115
116 public static class FSFactory {
117
118
119
120 protected FSFactory() {
121
122 }
123
124
125
126
127
128
129
130 public FS detect(Boolean cygwinUsed) {
131 if (SystemReader.getInstance().isWindows()) {
132 if (cygwinUsed == null)
133 cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
134 if (cygwinUsed.booleanValue())
135 return new FS_Win32_Cygwin();
136 else
137 return new FS_Win32();
138 } else {
139 return new FS_POSIX();
140 }
141 }
142 }
143
144
145
146
147
148
149
150 public static class ExecutionResult {
151 private TemporaryBuffer stdout;
152
153 private TemporaryBuffer stderr;
154
155 private int rc;
156
157
158
159
160
161
162 public ExecutionResult(TemporaryBuffer./../org/eclipse/jgit/util/TemporaryBuffer.html#TemporaryBuffer">TemporaryBuffer stdout, TemporaryBuffer stderr,
163 int rc) {
164 this.stdout = stdout;
165 this.stderr = stderr;
166 this.rc = rc;
167 }
168
169
170
171
172 public TemporaryBuffer getStdout() {
173 return stdout;
174 }
175
176
177
178
179 public TemporaryBuffer getStderr() {
180 return stderr;
181 }
182
183
184
185
186 public int getRc() {
187 return rc;
188 }
189 }
190
191 private static final class FileStoreAttributeCache {
192
193
194
195 private static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
196 .ofMillis(2000);
197
198 private static final Map<FileStore, FileStoreAttributeCache> attributeCache = new ConcurrentHashMap<>();
199
200 static Duration getFsTimestampResolution(Path file) {
201 try {
202 Path dir = Files.isDirectory(file) ? file : file.getParent();
203 if (!dir.toFile().canWrite()) {
204
205
206 return FALLBACK_TIMESTAMP_RESOLUTION;
207 }
208 FileStore s = Files.getFileStore(dir);
209 FileStoreAttributeCache c = attributeCache.get(s);
210 if (c == null) {
211 c = new FileStoreAttributeCache(dir);
212 attributeCache.put(s, c);
213 if (LOG.isDebugEnabled()) {
214 LOG.debug(c.toString());
215 }
216 }
217 return c.getFsTimestampResolution();
218
219 } catch (IOException | InterruptedException e) {
220 LOG.warn(e.getMessage(), e);
221 return FALLBACK_TIMESTAMP_RESOLUTION;
222 }
223 }
224
225 private Duration fsTimestampResolution;
226
227 Duration getFsTimestampResolution() {
228 return fsTimestampResolution;
229 }
230
231 private FileStoreAttributeCache(Path dir)
232 throws IOException, InterruptedException {
233 Path probe = dir.resolve(".probe-" + UUID.randomUUID());
234 Files.createFile(probe);
235 try {
236 FileTime startTime = Files.getLastModifiedTime(probe);
237 FileTime actTime = startTime;
238 long sleepTime = 512;
239 while (actTime.compareTo(startTime) <= 0) {
240 TimeUnit.NANOSECONDS.sleep(sleepTime);
241 FileUtils.touch(probe);
242 actTime = Files.getLastModifiedTime(probe);
243
244 if (sleepTime < 100_000_000L) {
245 sleepTime = sleepTime * 2;
246 }
247 }
248 fsTimestampResolution = Duration.between(startTime.toInstant(),
249 actTime.toInstant());
250 } catch (AccessDeniedException e) {
251 LOG.error(e.getLocalizedMessage(), e);
252 } finally {
253 Files.delete(probe);
254 }
255 }
256
257 @SuppressWarnings("nls")
258 @Override
259 public String toString() {
260 return "FileStoreAttributeCache[" + attributeCache.keySet()
261 .stream()
262 .map(key -> "FileStore[" + key + "]: fsTimestampResolution="
263 + attributeCache.get(key).getFsTimestampResolution())
264 .collect(Collectors.joining(",\n")) + "]";
265 }
266 }
267
268
269 public static final FS DETECTED = detect();
270
271 private volatile static FSFactory factory;
272
273
274
275
276
277
278 public static FS detect() {
279 return detect(null);
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302 public static FS detect(Boolean cygwinUsed) {
303 if (factory == null) {
304 factory = new FS.FSFactory();
305 }
306 return factory.detect(cygwinUsed);
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320 public static Duration getFsTimerResolution(@NonNull Path dir) {
321 return FileStoreAttributeCache.getFsTimestampResolution(dir);
322 }
323
324 private volatile Holder<File> userHome;
325
326 private volatile Holder<File> gitSystemConfig;
327
328
329
330
331 protected FS() {
332
333 }
334
335
336
337
338
339
340
341 protected FSme="FS" href="../../../../org/eclipse/jgit/util/FS.html#FS">FS(FS src) {
342 userHome = src.userHome;
343 gitSystemConfig = src.gitSystemConfig;
344 }
345
346
347
348
349
350
351 public abstract FS newInstance();
352
353
354
355
356
357
358
359 public abstract boolean supportsExecute();
360
361
362
363
364
365
366
367
368
369
370
371
372 public boolean supportsAtomicCreateNewFile() {
373 return true;
374 }
375
376
377
378
379
380
381
382
383 public boolean supportsSymlinks() {
384 return false;
385 }
386
387
388
389
390
391
392 public abstract boolean isCaseSensitive();
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 public abstract boolean canExecute(File f);
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423 public abstract boolean setExecute(File f, boolean canExec);
424
425
426
427
428
429
430
431
432
433
434
435
436 public long lastModified(File f) throws IOException {
437 return FileUtils.lastModified(f);
438 }
439
440
441
442
443
444
445
446
447
448
449
450
451 public void setLastModified(File f, long time) throws IOException {
452 FileUtils.setLastModified(f, time);
453 }
454
455
456
457
458
459
460
461
462
463
464
465 public long length(File path) throws IOException {
466 return FileUtils.getLength(path);
467 }
468
469
470
471
472
473
474
475
476
477
478 public void delete(File f) throws IOException {
479 FileUtils.delete(f);
480 }
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500 public File resolve(File dir, String name) {
501 final File abspn = new File(name);
502 if (abspn.isAbsolute())
503 return abspn;
504 return new File(dir, name);
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518 public File userHome() {
519 Holder<File> p = userHome;
520 if (p == null) {
521 p = new Holder<>(userHomeImpl());
522 userHome = p;
523 }
524 return p.value;
525 }
526
527
528
529
530
531
532
533
534
535 public FS setUserHome(File path) {
536 userHome = new Holder<>(path);
537 return this;
538 }
539
540
541
542
543
544
545 public abstract boolean retryFailedLockFileCommit();
546
547
548
549
550
551
552
553
554
555
556 public BasicFileAttributes fileAttributes(File file) throws IOException {
557 return FileUtils.fileAttributes(file);
558 }
559
560
561
562
563
564
565 protected File userHomeImpl() {
566 final String home = AccessController.doPrivileged(
567 (PrivilegedAction<String>) () -> System.getProperty("user.home")
568 );
569 if (home == null || home.length() == 0)
570 return null;
571 return new File(home).getAbsoluteFile();
572 }
573
574
575
576
577
578
579
580
581
582
583
584
585 protected static File searchPath(String path, String... lookFor) {
586 if (path == null)
587 return null;
588
589 for (String p : path.split(File.pathSeparator)) {
590 for (String command : lookFor) {
591 final File e = new File(p, command);
592 if (e.isFile())
593 return e.getAbsoluteFile();
594 }
595 }
596 return null;
597 }
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613 @Nullable
614 protected static String readPipe(File dir, String[] command,
615 String encoding) throws CommandFailedException {
616 return readPipe(dir, command, encoding, null);
617 }
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637 @Nullable
638 protected static String readPipe(File dir, String[] command,
639 String encoding, Map<String, String> env)
640 throws CommandFailedException {
641 final boolean debug = LOG.isDebugEnabled();
642 try {
643 if (debug) {
644 LOG.debug("readpipe " + Arrays.asList(command) + ","
645 + dir);
646 }
647 ProcessBuilder pb = new ProcessBuilder(command);
648 pb.directory(dir);
649 if (env != null) {
650 pb.environment().putAll(env);
651 }
652 Process p;
653 try {
654 p = pb.start();
655 } catch (IOException e) {
656
657 throw new CommandFailedException(-1, e.getMessage(), e);
658 }
659 p.getOutputStream().close();
660 GobblerThread gobbler = new GobblerThread(p, command, dir);
661 gobbler.start();
662 String r = null;
663 try (BufferedReader lineRead = new BufferedReader(
664 new InputStreamReader(p.getInputStream(), encoding))) {
665 r = lineRead.readLine();
666 if (debug) {
667 LOG.debug("readpipe may return '" + r + "'");
668 LOG.debug("remaining output:\n");
669 String l;
670 while ((l = lineRead.readLine()) != null) {
671 LOG.debug(l);
672 }
673 }
674 }
675
676 for (;;) {
677 try {
678 int rc = p.waitFor();
679 gobbler.join();
680 if (rc == 0 && !gobbler.fail.get()) {
681 return r;
682 } else {
683 if (debug) {
684 LOG.debug("readpipe rc=" + rc);
685 }
686 throw new CommandFailedException(rc,
687 gobbler.errorMessage.get(),
688 gobbler.exception.get());
689 }
690 } catch (InterruptedException ie) {
691
692 }
693 }
694 } catch (IOException e) {
695 LOG.error("Caught exception in FS.readPipe()", e);
696 }
697 if (debug) {
698 LOG.debug("readpipe returns null");
699 }
700 return null;
701 }
702
703 private static class GobblerThread extends Thread {
704
705
706 private static final int PROCESS_EXIT_TIMEOUT = 5;
707
708 private final Process p;
709 private final String desc;
710 private final String dir;
711 final AtomicBoolean fail = new AtomicBoolean();
712 final AtomicReference<String> errorMessage = new AtomicReference<>();
713 final AtomicReference<Throwable> exception = new AtomicReference<>();
714
715 GobblerThread(Process p, String[] command, File dir) {
716 this.p = p;
717 this.desc = Arrays.toString(command);
718 this.dir = Objects.toString(dir);
719 }
720
721 @Override
722 public void run() {
723 StringBuilder err = new StringBuilder();
724 try (InputStream is = p.getErrorStream()) {
725 int ch;
726 while ((ch = is.read()) != -1) {
727 err.append((char) ch);
728 }
729 } catch (IOException e) {
730 if (waitForProcessCompletion(e) && p.exitValue() != 0) {
731 setError(e, e.getMessage(), p.exitValue());
732 fail.set(true);
733 } else {
734
735
736 }
737 } finally {
738 if (waitForProcessCompletion(null) && err.length() > 0) {
739 setError(null, err.toString(), p.exitValue());
740 if (p.exitValue() != 0) {
741 fail.set(true);
742 }
743 }
744 }
745 }
746
747 @SuppressWarnings("boxing")
748 private boolean waitForProcessCompletion(IOException originalError) {
749 try {
750 if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
751 setError(originalError, MessageFormat.format(
752 JGitText.get().commandClosedStderrButDidntExit,
753 desc, PROCESS_EXIT_TIMEOUT), -1);
754 fail.set(true);
755 return false;
756 }
757 } catch (InterruptedException e) {
758 setError(originalError, MessageFormat.format(
759 JGitText.get().threadInterruptedWhileRunning, desc), -1);
760 fail.set(true);
761 return false;
762 }
763 return true;
764 }
765
766 private void setError(IOException e, String message, int exitCode) {
767 exception.set(e);
768 errorMessage.set(MessageFormat.format(
769 JGitText.get().exceptionCaughtDuringExecutionOfCommand,
770 desc, dir, Integer.valueOf(exitCode), message));
771 }
772 }
773
774
775
776
777
778
779
780
781 protected abstract File discoverGitExe();
782
783
784
785
786
787
788
789
790 protected File discoverGitSystemConfig() {
791 File gitExe = discoverGitExe();
792 if (gitExe == null) {
793 return null;
794 }
795
796
797 String v;
798 try {
799 v = readPipe(gitExe.getParentFile(),
800 new String[] { "git", "--version" },
801 Charset.defaultCharset().name());
802 } catch (CommandFailedException e) {
803 LOG.warn(e.getMessage());
804 return null;
805 }
806 if (StringUtils.isEmptyOrNull(v)
807 || (v != null && v.startsWith("jgit"))) {
808 return null;
809 }
810
811
812
813 Map<String, String> env = new HashMap<>();
814 env.put("GIT_EDITOR", "echo");
815
816 String w;
817 try {
818 w = readPipe(gitExe.getParentFile(),
819 new String[] { "git", "config", "--system", "--edit" },
820 Charset.defaultCharset().name(), env);
821 } catch (CommandFailedException e) {
822 LOG.warn(e.getMessage());
823 return null;
824 }
825 if (StringUtils.isEmptyOrNull(w)) {
826 return null;
827 }
828
829 return new File(w);
830 }
831
832
833
834
835
836
837
838
839 public File getGitSystemConfig() {
840 if (gitSystemConfig == null) {
841 gitSystemConfig = new Holder<>(discoverGitSystemConfig());
842 }
843 return gitSystemConfig.value;
844 }
845
846
847
848
849
850
851
852
853
854 public FS setGitSystemConfig(File configFile) {
855 gitSystemConfig = new Holder<>(configFile);
856 return this;
857 }
858
859
860
861
862
863
864
865
866
867
868 protected static File resolveGrandparentFile(File grandchild) {
869 if (grandchild != null) {
870 File parent = grandchild.getParentFile();
871 if (parent != null)
872 return parent.getParentFile();
873 }
874 return null;
875 }
876
877
878
879
880
881
882
883
884
885
886 public String readSymLink(File path) throws IOException {
887 return FileUtils.readSymLink(path);
888 }
889
890
891
892
893
894
895
896
897
898
899 public boolean isSymLink(File path) throws IOException {
900 return FileUtils.isSymlink(path);
901 }
902
903
904
905
906
907
908
909
910
911
912 public boolean exists(File path) {
913 return FileUtils.exists(path);
914 }
915
916
917
918
919
920
921
922
923
924
925 public boolean isDirectory(File path) {
926 return FileUtils.isDirectory(path);
927 }
928
929
930
931
932
933
934
935
936
937
938 public boolean isFile(File path) {
939 return FileUtils.isFile(path);
940 }
941
942
943
944
945
946
947
948
949
950
951
952
953 public boolean isHidden(File path) throws IOException {
954 return FileUtils.isHidden(path);
955 }
956
957
958
959
960
961
962
963
964
965
966
967 public void setHidden(File path, boolean hidden) throws IOException {
968 FileUtils.setHidden(path, hidden);
969 }
970
971
972
973
974
975
976
977
978
979
980
981 public void createSymLink(File path, String target) throws IOException {
982 FileUtils.createSymLink(path, target);
983 }
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998 @Deprecated
999 public boolean createNewFile(File path) throws IOException {
1000 return path.createNewFile();
1001 }
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012 public static class LockToken implements Closeable {
1013 private boolean isCreated;
1014
1015 private Optional<Path> link;
1016
1017 LockToken(boolean isCreated, Optional<Path> link) {
1018 this.isCreated = isCreated;
1019 this.link = link;
1020 }
1021
1022
1023
1024
1025 public boolean isCreated() {
1026 return isCreated;
1027 }
1028
1029 @Override
1030 public void close() {
1031 if (!link.isPresent()) {
1032 return;
1033 }
1034 Path p = link.get();
1035 if (!Files.exists(p)) {
1036 return;
1037 }
1038 try {
1039 Files.delete(p);
1040 } catch (IOException e) {
1041 LOG.error(MessageFormat
1042 .format(JGitText.get().closeLockTokenFailed, this), e);
1043 }
1044 }
1045
1046 @Override
1047 public String toString() {
1048 return "LockToken [lockCreated=" + isCreated +
1049 ", link="
1050 + (link.isPresent() ? link.get().getFileName() + "]"
1051 : "<null>]");
1052 }
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067 public LockToken createNewFileAtomic(File path) throws IOException {
1068 return new LockToken(path.createNewFile(), Optional.empty());
1069 }
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085 public String relativize(String base, String other) {
1086 return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
1087 }
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100 public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
1101 final File[] all = directory.listFiles();
1102 if (all == null) {
1103 return NO_ENTRIES;
1104 }
1105 final Entry[] result = new Entry[all.length];
1106 for (int i = 0; i < result.length; i++) {
1107 result[i] = new FileEntry(all[i], this, fileModeStrategy);
1108 }
1109 return result;
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134 public ProcessResult runHookIfPresent(Repository repository,
1135 final String hookName,
1136 String[] args) throws JGitInternalException {
1137 return runHookIfPresent(repository, hookName, args, System.out, System.err,
1138 null);
1139 }
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169 public ProcessResult runHookIfPresent(Repository repository,
1170 final String hookName,
1171 String[] args, PrintStream outRedirect, PrintStream errRedirect,
1172 String stdinArgs) throws JGitInternalException {
1173 return new ProcessResult(Status.NOT_SUPPORTED);
1174 }
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205 protected ProcessResult internalRunHookIfPresent(Repository repository,
1206 final String hookName, String[] args, PrintStream outRedirect,
1207 PrintStream errRedirect, String stdinArgs)
1208 throws JGitInternalException {
1209 final File hookFile = findHook(repository, hookName);
1210 if (hookFile == null)
1211 return new ProcessResult(Status.NOT_PRESENT);
1212
1213 final String hookPath = hookFile.getAbsolutePath();
1214 final File runDirectory;
1215 if (repository.isBare())
1216 runDirectory = repository.getDirectory();
1217 else
1218 runDirectory = repository.getWorkTree();
1219 final String cmd = relativize(runDirectory.getAbsolutePath(),
1220 hookPath);
1221 ProcessBuilder hookProcess = runInShell(cmd, args);
1222 hookProcess.directory(runDirectory);
1223 Map<String, String> environment = hookProcess.environment();
1224 environment.put(Constants.GIT_DIR_KEY,
1225 repository.getDirectory().getAbsolutePath());
1226 if (!repository.isBare()) {
1227 environment.put(Constants.GIT_WORK_TREE_KEY,
1228 repository.getWorkTree().getAbsolutePath());
1229 }
1230 try {
1231 return new ProcessResult(runProcess(hookProcess, outRedirect,
1232 errRedirect, stdinArgs), Status.OK);
1233 } catch (IOException e) {
1234 throw new JGitInternalException(MessageFormat.format(
1235 JGitText.get().exceptionCaughtDuringExecutionOfHook,
1236 hookName), e);
1237 } catch (InterruptedException e) {
1238 throw new JGitInternalException(MessageFormat.format(
1239 JGitText.get().exceptionHookExecutionInterrupted,
1240 hookName), e);
1241 }
1242 }
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256 public File findHook(Repository repository, String hookName) {
1257 File gitDir = repository.getDirectory();
1258 if (gitDir == null)
1259 return null;
1260 final File hookFile = new File(new File(gitDir,
1261 Constants.HOOKS), hookName);
1262 return hookFile.isFile() ? hookFile : null;
1263 }
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290 public int runProcess(ProcessBuilder processBuilder,
1291 OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1292 throws IOException, InterruptedException {
1293 InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1294 stdinArgs.getBytes(UTF_8));
1295 return runProcess(processBuilder, outRedirect, errRedirect, in);
1296 }
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326 public int runProcess(ProcessBuilder processBuilder,
1327 OutputStream outRedirect, OutputStream errRedirect,
1328 InputStream inRedirect) throws IOException,
1329 InterruptedException {
1330 final ExecutorService executor = Executors.newFixedThreadPool(2);
1331 Process process = null;
1332
1333
1334 IOException ioException = null;
1335 try {
1336 process = processBuilder.start();
1337 executor.execute(
1338 new StreamGobbler(process.getErrorStream(), errRedirect));
1339 executor.execute(
1340 new StreamGobbler(process.getInputStream(), outRedirect));
1341 @SuppressWarnings("resource")
1342 OutputStream outputStream = process.getOutputStream();
1343 try {
1344 if (inRedirect != null) {
1345 new StreamGobbler(inRedirect, outputStream).copy();
1346 }
1347 } finally {
1348 try {
1349 outputStream.close();
1350 } catch (IOException e) {
1351
1352
1353
1354
1355
1356
1357 }
1358 }
1359 return process.waitFor();
1360 } catch (IOException e) {
1361 ioException = e;
1362 } finally {
1363 shutdownAndAwaitTermination(executor);
1364 if (process != null) {
1365 try {
1366 process.waitFor();
1367 } catch (InterruptedException e) {
1368
1369
1370
1371
1372 Thread.interrupted();
1373 }
1374
1375
1376
1377 if (inRedirect != null) {
1378 inRedirect.close();
1379 }
1380 try {
1381 process.getErrorStream().close();
1382 } catch (IOException e) {
1383 ioException = ioException != null ? ioException : e;
1384 }
1385 try {
1386 process.getInputStream().close();
1387 } catch (IOException e) {
1388 ioException = ioException != null ? ioException : e;
1389 }
1390 try {
1391 process.getOutputStream().close();
1392 } catch (IOException e) {
1393 ioException = ioException != null ? ioException : e;
1394 }
1395 process.destroy();
1396 }
1397 }
1398
1399 throw ioException;
1400 }
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1416 boolean hasShutdown = true;
1417 pool.shutdown();
1418 try {
1419
1420 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1421 pool.shutdownNow();
1422
1423 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1424 hasShutdown = false;
1425 }
1426 } catch (InterruptedException ie) {
1427
1428 pool.shutdownNow();
1429
1430 Thread.currentThread().interrupt();
1431 hasShutdown = false;
1432 }
1433 return hasShutdown;
1434 }
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448 public abstract ProcessBuilder runInShell(String cmd, String[] args);
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462 public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1463 throws IOException, InterruptedException {
1464 try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1465 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1466 1024 * 1024)) {
1467 int rc = runProcess(pb, stdout, stderr, in);
1468 return new ExecutionResult(stdout, stderr, rc);
1469 }
1470 }
1471
1472 private static class Holder<V> {
1473 final V value;
1474
1475 Holder(V value) {
1476 this.value = value;
1477 }
1478 }
1479
1480
1481
1482
1483
1484
1485 public static class Attributes {
1486
1487
1488
1489
1490 public boolean isDirectory() {
1491 return isDirectory;
1492 }
1493
1494
1495
1496
1497 public boolean isExecutable() {
1498 return isExecutable;
1499 }
1500
1501
1502
1503
1504 public boolean isSymbolicLink() {
1505 return isSymbolicLink;
1506 }
1507
1508
1509
1510
1511 public boolean isRegularFile() {
1512 return isRegularFile;
1513 }
1514
1515
1516
1517
1518 public long getCreationTime() {
1519 return creationTime;
1520 }
1521
1522
1523
1524
1525
1526 public long getLastModifiedTime() {
1527 return lastModifiedTime;
1528 }
1529
1530 private final boolean isDirectory;
1531
1532 private final boolean isSymbolicLink;
1533
1534 private final boolean isRegularFile;
1535
1536 private final long creationTime;
1537
1538 private final long lastModifiedTime;
1539
1540 private final boolean isExecutable;
1541
1542 private final File file;
1543
1544 private final boolean exists;
1545
1546
1547
1548
1549 protected long length = -1;
1550
1551 final FS fs;
1552
1553 Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1554 boolean isExecutable, boolean isSymbolicLink,
1555 boolean isRegularFile, long creationTime,
1556 long lastModifiedTime, long length) {
1557 this.fs = fs;
1558 this.file = file;
1559 this.exists = exists;
1560 this.isDirectory = isDirectory;
1561 this.isExecutable = isExecutable;
1562 this.isSymbolicLink = isSymbolicLink;
1563 this.isRegularFile = isRegularFile;
1564 this.creationTime = creationTime;
1565 this.lastModifiedTime = lastModifiedTime;
1566 this.length = length;
1567 }
1568
1569
1570
1571
1572
1573
1574
1575
1576 public Attributes(File path, FS fs) {
1577 this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1578 }
1579
1580
1581
1582
1583 public long getLength() {
1584 if (length == -1)
1585 return length = file.length();
1586 return length;
1587 }
1588
1589
1590
1591
1592 public String getName() {
1593 return file.getName();
1594 }
1595
1596
1597
1598
1599 public File getFile() {
1600 return file;
1601 }
1602
1603 boolean exists() {
1604 return exists;
1605 }
1606 }
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616 public Attributes getAttributes(File path) {
1617 boolean isDirectory = isDirectory(path);
1618 boolean isFile = !isDirectory && path.isFile();
1619 assert path.exists() == isDirectory || isFile;
1620 boolean exists = isDirectory || isFile;
1621 boolean canExecute = exists && !isDirectory && canExecute(path);
1622 boolean isSymlink = false;
1623 long lastModified = exists ? path.lastModified() : 0L;
1624 long createTime = 0L;
1625 return new Attributes(this, path, exists, isDirectory, canExecute,
1626 isSymlink, isFile, createTime, lastModified, -1);
1627 }
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637 public File normalize(File file) {
1638 return file;
1639 }
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649 public String normalize(String name) {
1650 return name;
1651 }
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665 private static class StreamGobbler implements Runnable {
1666 private InputStream in;
1667
1668 private OutputStream out;
1669
1670 public StreamGobbler(InputStream stream, OutputStream output) {
1671 this.in = stream;
1672 this.out = output;
1673 }
1674
1675 @Override
1676 public void run() {
1677 try {
1678 copy();
1679 } catch (IOException e) {
1680
1681 }
1682 }
1683
1684 void copy() throws IOException {
1685 boolean writeFailure = false;
1686 byte buffer[] = new byte[4096];
1687 int readBytes;
1688 while ((readBytes = in.read(buffer)) != -1) {
1689
1690
1691
1692 if (!writeFailure && out != null) {
1693 try {
1694 out.write(buffer, 0, readBytes);
1695 out.flush();
1696 } catch (IOException e) {
1697 writeFailure = true;
1698 }
1699 }
1700 }
1701 }
1702 }
1703 }