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 java.io.BufferedReader;
47 import java.io.ByteArrayInputStream;
48 import java.io.File;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.InputStreamReader;
52 import java.io.OutputStream;
53 import java.io.PrintStream;
54 import java.nio.charset.Charset;
55 import java.security.AccessController;
56 import java.security.PrivilegedAction;
57 import java.text.MessageFormat;
58 import java.util.Arrays;
59 import java.util.HashMap;
60 import java.util.Map;
61 import java.util.Objects;
62 import java.util.concurrent.ExecutorService;
63 import java.util.concurrent.Executors;
64 import java.util.concurrent.TimeUnit;
65 import java.util.concurrent.atomic.AtomicBoolean;
66 import java.util.concurrent.atomic.AtomicReference;
67
68 import org.eclipse.jgit.annotations.Nullable;
69 import org.eclipse.jgit.api.errors.JGitInternalException;
70 import org.eclipse.jgit.errors.CommandFailedException;
71 import org.eclipse.jgit.internal.JGitText;
72 import org.eclipse.jgit.lib.Constants;
73 import org.eclipse.jgit.lib.Repository;
74 import org.eclipse.jgit.util.ProcessResult.Status;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78
79 public abstract class FS {
80
81
82
83
84
85
86 public static class FSFactory {
87
88
89
90 protected FSFactory() {
91
92 }
93
94
95
96
97
98
99
100 public FS detect(Boolean cygwinUsed) {
101 if (SystemReader.getInstance().isWindows()) {
102 if (cygwinUsed == null)
103 cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
104 if (cygwinUsed.booleanValue())
105 return new FS_Win32_Cygwin();
106 else
107 return new FS_Win32();
108 } else {
109 return new FS_POSIX();
110 }
111 }
112 }
113
114
115
116
117
118
119
120 public static class ExecutionResult {
121 private TemporaryBuffer stdout;
122
123 private TemporaryBuffer stderr;
124
125 private int rc;
126
127
128
129
130
131
132 public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
133 int rc) {
134 this.stdout = stdout;
135 this.stderr = stderr;
136 this.rc = rc;
137 }
138
139
140
141
142 public TemporaryBuffer getStdout() {
143 return stdout;
144 }
145
146
147
148
149 public TemporaryBuffer getStderr() {
150 return stderr;
151 }
152
153
154
155
156 public int getRc() {
157 return rc;
158 }
159 }
160
161 private final static Logger LOG = LoggerFactory.getLogger(FS.class);
162
163
164 public static final FS DETECTED = detect();
165
166 private volatile static FSFactory factory;
167
168
169
170
171
172
173 public static FS detect() {
174 return detect(null);
175 }
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198 public static FS detect(Boolean cygwinUsed) {
199 if (factory == null) {
200 factory = new FS.FSFactory();
201 }
202 return factory.detect(cygwinUsed);
203 }
204
205 private volatile Holder<File> userHome;
206
207 private volatile Holder<File> gitSystemConfig;
208
209
210
211
212 protected FS() {
213
214 }
215
216
217
218
219
220
221
222 protected FS(FS src) {
223 userHome = src.userHome;
224 gitSystemConfig = src.gitSystemConfig;
225 }
226
227
228 public abstract FS newInstance();
229
230
231
232
233
234
235
236 public abstract boolean supportsExecute();
237
238
239
240
241
242
243
244
245 public boolean supportsSymlinks() {
246 return false;
247 }
248
249
250
251
252
253
254 public abstract boolean isCaseSensitive();
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 public abstract boolean canExecute(File f);
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 public abstract boolean setExecute(File f, boolean canExec);
286
287
288
289
290
291
292
293
294
295
296
297 public long lastModified(File f) throws IOException {
298 return FileUtils.lastModified(f);
299 }
300
301
302
303
304
305
306
307
308
309
310 public void setLastModified(File f, long time) throws IOException {
311 FileUtils.setLastModified(f, time);
312 }
313
314
315
316
317
318
319
320
321
322
323 public long length(File path) throws IOException {
324 return FileUtils.getLength(path);
325 }
326
327
328
329
330
331
332
333
334
335 public void delete(File f) throws IOException {
336 FileUtils.delete(f);
337 }
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357 public File resolve(final File dir, final String name) {
358 final File abspn = new File(name);
359 if (abspn.isAbsolute())
360 return abspn;
361 return new File(dir, name);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375 public File userHome() {
376 Holder<File> p = userHome;
377 if (p == null) {
378 p = new Holder<>(userHomeImpl());
379 userHome = p;
380 }
381 return p.value;
382 }
383
384
385
386
387
388
389
390
391
392 public FS setUserHome(File path) {
393 userHome = new Holder<>(path);
394 return this;
395 }
396
397
398
399
400
401
402 public abstract boolean retryFailedLockFileCommit();
403
404
405
406
407
408
409 protected File userHomeImpl() {
410 final String home = AccessController
411 .doPrivileged(new PrivilegedAction<String>() {
412 @Override
413 public String run() {
414 return System.getProperty("user.home");
415 }
416 });
417 if (home == null || home.length() == 0)
418 return null;
419 return new File(home).getAbsoluteFile();
420 }
421
422
423
424
425
426
427
428
429
430
431
432
433 protected static File searchPath(final String path, final String... lookFor) {
434 if (path == null)
435 return null;
436
437 for (final String p : path.split(File.pathSeparator)) {
438 for (String command : lookFor) {
439 final File e = new File(p, command);
440 if (e.isFile())
441 return e.getAbsoluteFile();
442 }
443 }
444 return null;
445 }
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461 @Nullable
462 protected static String readPipe(File dir, String[] command,
463 String encoding) throws CommandFailedException {
464 return readPipe(dir, command, encoding, null);
465 }
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 @Nullable
486 protected static String readPipe(File dir, String[] command,
487 String encoding, Map<String, String> env)
488 throws CommandFailedException {
489 final boolean debug = LOG.isDebugEnabled();
490 try {
491 if (debug) {
492 LOG.debug("readpipe " + Arrays.asList(command) + ","
493 + dir);
494 }
495 ProcessBuilder pb = new ProcessBuilder(command);
496 pb.directory(dir);
497 if (env != null) {
498 pb.environment().putAll(env);
499 }
500 Process p;
501 try {
502 p = pb.start();
503 } catch (IOException e) {
504
505 throw new CommandFailedException(-1, e.getMessage(), e);
506 }
507 p.getOutputStream().close();
508 GobblerThread gobbler = new GobblerThread(p, command, dir);
509 gobbler.start();
510 String r = null;
511 try (BufferedReader lineRead = new BufferedReader(
512 new InputStreamReader(p.getInputStream(), encoding))) {
513 r = lineRead.readLine();
514 if (debug) {
515 LOG.debug("readpipe may return '" + r + "'");
516 LOG.debug("remaining output:\n");
517 String l;
518 while ((l = lineRead.readLine()) != null) {
519 LOG.debug(l);
520 }
521 }
522 }
523
524 for (;;) {
525 try {
526 int rc = p.waitFor();
527 gobbler.join();
528 if (rc == 0 && !gobbler.fail.get()) {
529 return r;
530 } else {
531 if (debug) {
532 LOG.debug("readpipe rc=" + rc);
533 }
534 throw new CommandFailedException(rc,
535 gobbler.errorMessage.get(),
536 gobbler.exception.get());
537 }
538 } catch (InterruptedException ie) {
539
540 }
541 }
542 } catch (IOException e) {
543 LOG.error("Caught exception in FS.readPipe()", e);
544 }
545 if (debug) {
546 LOG.debug("readpipe returns null");
547 }
548 return null;
549 }
550
551 private static class GobblerThread extends Thread {
552 private final Process p;
553 private final String desc;
554 private final String dir;
555 final AtomicBoolean fail = new AtomicBoolean();
556 final AtomicReference<String> errorMessage = new AtomicReference<>();
557 final AtomicReference<Throwable> exception = new AtomicReference<>();
558
559 GobblerThread(Process p, String[] command, File dir) {
560 this.p = p;
561 this.desc = Arrays.toString(command);
562 this.dir = Objects.toString(dir);
563 }
564
565 @Override
566 public void run() {
567 StringBuilder err = new StringBuilder();
568 try (InputStream is = p.getErrorStream()) {
569 int ch;
570 while ((ch = is.read()) != -1) {
571 err.append((char) ch);
572 }
573 } catch (IOException e) {
574 if (p.exitValue() != 0) {
575 setError(e, e.getMessage());
576 fail.set(true);
577 } else {
578
579 }
580 } finally {
581 if (err.length() > 0) {
582 setError(null, err.toString());
583 if (p.exitValue() != 0) {
584 fail.set(true);
585 }
586 }
587 }
588 }
589
590 private void setError(IOException e, String message) {
591 exception.set(e);
592 errorMessage.set(MessageFormat.format(
593 JGitText.get().exceptionCaughtDuringExcecutionOfCommand,
594 desc, dir, Integer.valueOf(p.exitValue()), message));
595 }
596 }
597
598
599
600
601
602
603 protected abstract File discoverGitExe();
604
605
606
607
608
609
610 protected File discoverGitSystemConfig() {
611 File gitExe = discoverGitExe();
612 if (gitExe == null) {
613 return null;
614 }
615
616
617 String v;
618 try {
619 v = readPipe(gitExe.getParentFile(),
620 new String[] { "git", "--version" },
621 Charset.defaultCharset().name());
622 } catch (CommandFailedException e) {
623 LOG.warn(e.getMessage());
624 return null;
625 }
626 if (StringUtils.isEmptyOrNull(v)
627 || (v != null && v.startsWith("jgit"))) {
628 return null;
629 }
630
631
632
633 Map<String, String> env = new HashMap<>();
634 env.put("GIT_EDITOR", "echo");
635
636 String w;
637 try {
638 w = readPipe(gitExe.getParentFile(),
639 new String[] { "git", "config", "--system", "--edit" },
640 Charset.defaultCharset().name(), env);
641 } catch (CommandFailedException e) {
642 LOG.warn(e.getMessage());
643 return null;
644 }
645 if (StringUtils.isEmptyOrNull(w)) {
646 return null;
647 }
648
649 return new File(w);
650 }
651
652
653
654
655
656
657 public File getGitSystemConfig() {
658 if (gitSystemConfig == null) {
659 gitSystemConfig = new Holder<>(discoverGitSystemConfig());
660 }
661 return gitSystemConfig.value;
662 }
663
664
665
666
667
668
669
670
671
672 public FS setGitSystemConfig(File configFile) {
673 gitSystemConfig = new Holder<>(configFile);
674 return this;
675 }
676
677
678
679
680
681
682
683 protected static File resolveGrandparentFile(File grandchild) {
684 if (grandchild != null) {
685 File parent = grandchild.getParentFile();
686 if (parent != null)
687 return parent.getParentFile();
688 }
689 return null;
690 }
691
692
693
694
695
696
697
698
699
700 public String readSymLink(File path) throws IOException {
701 return FileUtils.readSymLink(path);
702 }
703
704
705
706
707
708
709
710 public boolean isSymLink(File path) throws IOException {
711 return FileUtils.isSymlink(path);
712 }
713
714
715
716
717
718
719
720
721
722 public boolean exists(File path) {
723 return FileUtils.exists(path);
724 }
725
726
727
728
729
730
731
732
733
734 public boolean isDirectory(File path) {
735 return FileUtils.isDirectory(path);
736 }
737
738
739
740
741
742
743
744
745
746 public boolean isFile(File path) {
747 return FileUtils.isFile(path);
748 }
749
750
751
752
753
754
755
756
757 public boolean isHidden(File path) throws IOException {
758 return FileUtils.isHidden(path);
759 }
760
761
762
763
764
765
766
767
768
769 public void setHidden(File path, boolean hidden) throws IOException {
770 FileUtils.setHidden(path, hidden);
771 }
772
773
774
775
776
777
778
779
780
781 public void createSymLink(File path, String target) throws IOException {
782 FileUtils.createSymLink(path, target);
783 }
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798 public String relativize(String base, String other) {
799 return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
800 }
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824 public ProcessResult runHookIfPresent(Repository repository,
825 final String hookName,
826 String[] args) throws JGitInternalException {
827 return runHookIfPresent(repository, hookName, args, System.out, System.err,
828 null);
829 }
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859 public ProcessResult runHookIfPresent(Repository repository,
860 final String hookName,
861 String[] args, PrintStream outRedirect, PrintStream errRedirect,
862 String stdinArgs) throws JGitInternalException {
863 return new ProcessResult(Status.NOT_SUPPORTED);
864 }
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895 protected ProcessResult internalRunHookIfPresent(Repository repository,
896 final String hookName, String[] args, PrintStream outRedirect,
897 PrintStream errRedirect, String stdinArgs)
898 throws JGitInternalException {
899 final File hookFile = findHook(repository, hookName);
900 if (hookFile == null)
901 return new ProcessResult(Status.NOT_PRESENT);
902
903 final String hookPath = hookFile.getAbsolutePath();
904 final File runDirectory;
905 if (repository.isBare())
906 runDirectory = repository.getDirectory();
907 else
908 runDirectory = repository.getWorkTree();
909 final String cmd = relativize(runDirectory.getAbsolutePath(),
910 hookPath);
911 ProcessBuilder hookProcess = runInShell(cmd, args);
912 hookProcess.directory(runDirectory);
913 try {
914 return new ProcessResult(runProcess(hookProcess, outRedirect,
915 errRedirect, stdinArgs), Status.OK);
916 } catch (IOException e) {
917 throw new JGitInternalException(MessageFormat.format(
918 JGitText.get().exceptionCaughtDuringExecutionOfHook,
919 hookName), e);
920 } catch (InterruptedException e) {
921 throw new JGitInternalException(MessageFormat.format(
922 JGitText.get().exceptionHookExecutionInterrupted,
923 hookName), e);
924 }
925 }
926
927
928
929
930
931
932
933
934
935
936
937
938
939 public File findHook(Repository repository, final String hookName) {
940 File gitDir = repository.getDirectory();
941 if (gitDir == null)
942 return null;
943 final File hookFile = new File(new File(gitDir,
944 Constants.HOOKS), hookName);
945 return hookFile.isFile() ? hookFile : null;
946 }
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973 public int runProcess(ProcessBuilder processBuilder,
974 OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
975 throws IOException, InterruptedException {
976 InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
977 stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
978 return runProcess(processBuilder, outRedirect, errRedirect, in);
979 }
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009 public int runProcess(ProcessBuilder processBuilder,
1010 OutputStream outRedirect, OutputStream errRedirect,
1011 InputStream inRedirect) throws IOException,
1012 InterruptedException {
1013 final ExecutorService executor = Executors.newFixedThreadPool(2);
1014 Process process = null;
1015
1016
1017 IOException ioException = null;
1018 try {
1019 process = processBuilder.start();
1020 executor.execute(
1021 new StreamGobbler(process.getErrorStream(), errRedirect));
1022 executor.execute(
1023 new StreamGobbler(process.getInputStream(), outRedirect));
1024 OutputStream outputStream = process.getOutputStream();
1025 if (inRedirect != null) {
1026 new StreamGobbler(inRedirect, outputStream).copy();
1027 }
1028 try {
1029 outputStream.close();
1030 } catch (IOException e) {
1031
1032
1033
1034
1035
1036
1037 }
1038 return process.waitFor();
1039 } catch (IOException e) {
1040 ioException = e;
1041 } finally {
1042 shutdownAndAwaitTermination(executor);
1043 if (process != null) {
1044 try {
1045 process.waitFor();
1046 } catch (InterruptedException e) {
1047
1048
1049
1050
1051 Thread.interrupted();
1052 }
1053
1054
1055
1056 if (inRedirect != null) {
1057 inRedirect.close();
1058 }
1059 try {
1060 process.getErrorStream().close();
1061 } catch (IOException e) {
1062 ioException = ioException != null ? ioException : e;
1063 }
1064 try {
1065 process.getInputStream().close();
1066 } catch (IOException e) {
1067 ioException = ioException != null ? ioException : e;
1068 }
1069 try {
1070 process.getOutputStream().close();
1071 } catch (IOException e) {
1072 ioException = ioException != null ? ioException : e;
1073 }
1074 process.destroy();
1075 }
1076 }
1077
1078 throw ioException;
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1095 boolean hasShutdown = true;
1096 pool.shutdown();
1097 try {
1098
1099 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1100 pool.shutdownNow();
1101
1102 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1103 hasShutdown = false;
1104 }
1105 } catch (InterruptedException ie) {
1106
1107 pool.shutdownNow();
1108
1109 Thread.currentThread().interrupt();
1110 hasShutdown = false;
1111 }
1112 return hasShutdown;
1113 }
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127 public abstract ProcessBuilder runInShell(String cmd, String[] args);
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141 public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1142 throws IOException, InterruptedException {
1143 TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1144 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
1145 try {
1146 int rc = runProcess(pb, stdout, stderr, in);
1147 return new ExecutionResult(stdout, stderr, rc);
1148 } finally {
1149 stdout.close();
1150 stderr.close();
1151 }
1152 }
1153
1154 private static class Holder<V> {
1155 final V value;
1156
1157 Holder(V value) {
1158 this.value = value;
1159 }
1160 }
1161
1162
1163
1164
1165
1166
1167 public static class Attributes {
1168
1169
1170
1171
1172 public boolean isDirectory() {
1173 return isDirectory;
1174 }
1175
1176
1177
1178
1179 public boolean isExecutable() {
1180 return isExecutable;
1181 }
1182
1183
1184
1185
1186 public boolean isSymbolicLink() {
1187 return isSymbolicLink;
1188 }
1189
1190
1191
1192
1193 public boolean isRegularFile() {
1194 return isRegularFile;
1195 }
1196
1197
1198
1199
1200 public long getCreationTime() {
1201 return creationTime;
1202 }
1203
1204
1205
1206
1207
1208 public long getLastModifiedTime() {
1209 return lastModifiedTime;
1210 }
1211
1212 private final boolean isDirectory;
1213
1214 private final boolean isSymbolicLink;
1215
1216 private final boolean isRegularFile;
1217
1218 private final long creationTime;
1219
1220 private final long lastModifiedTime;
1221
1222 private final boolean isExecutable;
1223
1224 private final File file;
1225
1226 private final boolean exists;
1227
1228
1229
1230
1231 protected long length = -1;
1232
1233 final FS fs;
1234
1235 Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1236 boolean isExecutable, boolean isSymbolicLink,
1237 boolean isRegularFile, long creationTime,
1238 long lastModifiedTime, long length) {
1239 this.fs = fs;
1240 this.file = file;
1241 this.exists = exists;
1242 this.isDirectory = isDirectory;
1243 this.isExecutable = isExecutable;
1244 this.isSymbolicLink = isSymbolicLink;
1245 this.isRegularFile = isRegularFile;
1246 this.creationTime = creationTime;
1247 this.lastModifiedTime = lastModifiedTime;
1248 this.length = length;
1249 }
1250
1251
1252
1253
1254
1255
1256
1257
1258 public Attributes(File path, FS fs) {
1259 this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1260 }
1261
1262
1263
1264
1265 public long getLength() {
1266 if (length == -1)
1267 return length = file.length();
1268 return length;
1269 }
1270
1271
1272
1273
1274 public String getName() {
1275 return file.getName();
1276 }
1277
1278
1279
1280
1281 public File getFile() {
1282 return file;
1283 }
1284
1285 boolean exists() {
1286 return exists;
1287 }
1288 }
1289
1290
1291
1292
1293
1294
1295 public Attributes getAttributes(File path) {
1296 boolean isDirectory = isDirectory(path);
1297 boolean isFile = !isDirectory && path.isFile();
1298 assert path.exists() == isDirectory || isFile;
1299 boolean exists = isDirectory || isFile;
1300 boolean canExecute = exists && !isDirectory && canExecute(path);
1301 boolean isSymlink = false;
1302 long lastModified = exists ? path.lastModified() : 0L;
1303 long createTime = 0L;
1304 return new Attributes(this, path, exists, isDirectory, canExecute,
1305 isSymlink, isFile, createTime, lastModified, -1);
1306 }
1307
1308
1309
1310
1311
1312
1313
1314
1315 public File normalize(File file) {
1316 return file;
1317 }
1318
1319
1320
1321
1322
1323
1324
1325
1326 public String normalize(String name) {
1327 return name;
1328 }
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342 private static class StreamGobbler implements Runnable {
1343 private InputStream in;
1344
1345 private OutputStream out;
1346
1347 public StreamGobbler(InputStream stream, OutputStream output) {
1348 this.in = stream;
1349 this.out = output;
1350 }
1351
1352 @Override
1353 public void run() {
1354 try {
1355 copy();
1356 } catch (IOException e) {
1357
1358 }
1359 }
1360
1361 void copy() throws IOException {
1362 boolean writeFailure = false;
1363 byte buffer[] = new byte[4096];
1364 int readBytes;
1365 while ((readBytes = in.read(buffer)) != -1) {
1366
1367
1368
1369 if (!writeFailure && out != null) {
1370 try {
1371 out.write(buffer, 0, readBytes);
1372 out.flush();
1373 } catch (IOException e) {
1374 writeFailure = true;
1375 }
1376 }
1377 }
1378 }
1379 }
1380 }