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