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