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 }
644 } catch (InterruptedException e) {
645 LOG.error(MessageFormat.format(
646 JGitText.get().threadInterruptedWhileRunning, desc), e);
647 }
648 return false;
649 }
650
651 private void setError(IOException e, String message, int exitCode) {
652 exception.set(e);
653 errorMessage.set(MessageFormat.format(
654 JGitText.get().exceptionCaughtDuringExecutionOfCommand,
655 desc, dir, Integer.valueOf(exitCode), message));
656 }
657 }
658
659
660
661
662
663
664
665
666 protected abstract File discoverGitExe();
667
668
669
670
671
672
673
674
675 protected File discoverGitSystemConfig() {
676 File gitExe = discoverGitExe();
677 if (gitExe == null) {
678 return null;
679 }
680
681
682 String v;
683 try {
684 v = readPipe(gitExe.getParentFile(),
685 new String[] { "git", "--version" },
686 Charset.defaultCharset().name());
687 } catch (CommandFailedException e) {
688 LOG.warn(e.getMessage());
689 return null;
690 }
691 if (StringUtils.isEmptyOrNull(v)
692 || (v != null && v.startsWith("jgit"))) {
693 return null;
694 }
695
696
697
698 Map<String, String> env = new HashMap<>();
699 env.put("GIT_EDITOR", "echo");
700
701 String w;
702 try {
703 w = readPipe(gitExe.getParentFile(),
704 new String[] { "git", "config", "--system", "--edit" },
705 Charset.defaultCharset().name(), env);
706 } catch (CommandFailedException e) {
707 LOG.warn(e.getMessage());
708 return null;
709 }
710 if (StringUtils.isEmptyOrNull(w)) {
711 return null;
712 }
713
714 return new File(w);
715 }
716
717
718
719
720
721
722
723
724 public File getGitSystemConfig() {
725 if (gitSystemConfig == null) {
726 gitSystemConfig = new Holder<>(discoverGitSystemConfig());
727 }
728 return gitSystemConfig.value;
729 }
730
731
732
733
734
735
736
737
738
739 public FS setGitSystemConfig(File configFile) {
740 gitSystemConfig = new Holder<>(configFile);
741 return this;
742 }
743
744
745
746
747
748
749
750
751
752
753 protected static File resolveGrandparentFile(File grandchild) {
754 if (grandchild != null) {
755 File parent = grandchild.getParentFile();
756 if (parent != null)
757 return parent.getParentFile();
758 }
759 return null;
760 }
761
762
763
764
765
766
767
768
769
770
771 public String readSymLink(File path) throws IOException {
772 return FileUtils.readSymLink(path);
773 }
774
775
776
777
778
779
780
781
782
783
784 public boolean isSymLink(File path) throws IOException {
785 return FileUtils.isSymlink(path);
786 }
787
788
789
790
791
792
793
794
795
796
797 public boolean exists(File path) {
798 return FileUtils.exists(path);
799 }
800
801
802
803
804
805
806
807
808
809
810 public boolean isDirectory(File path) {
811 return FileUtils.isDirectory(path);
812 }
813
814
815
816
817
818
819
820
821
822
823 public boolean isFile(File path) {
824 return FileUtils.isFile(path);
825 }
826
827
828
829
830
831
832
833
834
835
836
837
838 public boolean isHidden(File path) throws IOException {
839 return FileUtils.isHidden(path);
840 }
841
842
843
844
845
846
847
848
849
850
851
852 public void setHidden(File path, boolean hidden) throws IOException {
853 FileUtils.setHidden(path, hidden);
854 }
855
856
857
858
859
860
861
862
863
864
865
866 public void createSymLink(File path, String target) throws IOException {
867 FileUtils.createSymLink(path, target);
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883 @Deprecated
884 public boolean createNewFile(File path) throws IOException {
885 return path.createNewFile();
886 }
887
888
889
890
891
892
893
894
895
896
897 public static class LockToken implements Closeable {
898 private boolean isCreated;
899
900 private Optional<Path> link;
901
902 LockToken(boolean isCreated, Optional<Path> link) {
903 this.isCreated = isCreated;
904 this.link = link;
905 }
906
907
908
909
910 public boolean isCreated() {
911 return isCreated;
912 }
913
914 @Override
915 public void close() {
916 if (link.isPresent()) {
917 try {
918 Files.delete(link.get());
919 } catch (IOException e) {
920 LOG.error(MessageFormat.format(JGitText.get().closeLockTokenFailed,
921 this), e);
922 }
923 }
924 }
925
926 @Override
927 public String toString() {
928 return "LockToken [lockCreated=" + isCreated +
929 ", link="
930 + (link.isPresent() ? link.get().getFileName() + "]"
931 : "<null>]");
932 }
933 }
934
935
936
937
938
939
940
941
942
943
944
945
946
947 public LockToken createNewFileAtomic(File path) throws IOException {
948 return new LockToken(path.createNewFile(), Optional.empty());
949 }
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965 public String relativize(String base, String other) {
966 return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
967 }
968
969
970
971
972
973
974
975
976
977
978
979
980 public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
981 final File[] all = directory.listFiles();
982 if (all == null) {
983 return NO_ENTRIES;
984 }
985 final Entry[] result = new Entry[all.length];
986 for (int i = 0; i < result.length; i++) {
987 result[i] = new FileEntry(all[i], this, fileModeStrategy);
988 }
989 return result;
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014 public ProcessResult runHookIfPresent(Repository repository,
1015 final String hookName,
1016 String[] args) throws JGitInternalException {
1017 return runHookIfPresent(repository, hookName, args, System.out, System.err,
1018 null);
1019 }
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 public ProcessResult runHookIfPresent(Repository repository,
1050 final String hookName,
1051 String[] args, PrintStream outRedirect, PrintStream errRedirect,
1052 String stdinArgs) throws JGitInternalException {
1053 return new ProcessResult(Status.NOT_SUPPORTED);
1054 }
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085 protected ProcessResult internalRunHookIfPresent(Repository repository,
1086 final String hookName, String[] args, PrintStream outRedirect,
1087 PrintStream errRedirect, String stdinArgs)
1088 throws JGitInternalException {
1089 final File hookFile = findHook(repository, hookName);
1090 if (hookFile == null)
1091 return new ProcessResult(Status.NOT_PRESENT);
1092
1093 final String hookPath = hookFile.getAbsolutePath();
1094 final File runDirectory;
1095 if (repository.isBare())
1096 runDirectory = repository.getDirectory();
1097 else
1098 runDirectory = repository.getWorkTree();
1099 final String cmd = relativize(runDirectory.getAbsolutePath(),
1100 hookPath);
1101 ProcessBuilder hookProcess = runInShell(cmd, args);
1102 hookProcess.directory(runDirectory);
1103 try {
1104 return new ProcessResult(runProcess(hookProcess, outRedirect,
1105 errRedirect, stdinArgs), Status.OK);
1106 } catch (IOException e) {
1107 throw new JGitInternalException(MessageFormat.format(
1108 JGitText.get().exceptionCaughtDuringExecutionOfHook,
1109 hookName), e);
1110 } catch (InterruptedException e) {
1111 throw new JGitInternalException(MessageFormat.format(
1112 JGitText.get().exceptionHookExecutionInterrupted,
1113 hookName), e);
1114 }
1115 }
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 public File findHook(Repository repository, String hookName) {
1130 File gitDir = repository.getDirectory();
1131 if (gitDir == null)
1132 return null;
1133 final File hookFile = new File(new File(gitDir,
1134 Constants.HOOKS), hookName);
1135 return hookFile.isFile() ? hookFile : null;
1136 }
1137
1138
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 public int runProcess(ProcessBuilder processBuilder,
1164 OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
1165 throws IOException, InterruptedException {
1166 InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
1167 stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
1168 return runProcess(processBuilder, outRedirect, errRedirect, in);
1169 }
1170
1171
1172
1173
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 public int runProcess(ProcessBuilder processBuilder,
1200 OutputStream outRedirect, OutputStream errRedirect,
1201 InputStream inRedirect) throws IOException,
1202 InterruptedException {
1203 final ExecutorService executor = Executors.newFixedThreadPool(2);
1204 Process process = null;
1205
1206
1207 IOException ioException = null;
1208 try {
1209 process = processBuilder.start();
1210 executor.execute(
1211 new StreamGobbler(process.getErrorStream(), errRedirect));
1212 executor.execute(
1213 new StreamGobbler(process.getInputStream(), outRedirect));
1214 @SuppressWarnings("resource")
1215 OutputStream outputStream = process.getOutputStream();
1216 try {
1217 if (inRedirect != null) {
1218 new StreamGobbler(inRedirect, outputStream).copy();
1219 }
1220 } finally {
1221 try {
1222 outputStream.close();
1223 } catch (IOException e) {
1224
1225
1226
1227
1228
1229
1230 }
1231 }
1232 return process.waitFor();
1233 } catch (IOException e) {
1234 ioException = e;
1235 } finally {
1236 shutdownAndAwaitTermination(executor);
1237 if (process != null) {
1238 try {
1239 process.waitFor();
1240 } catch (InterruptedException e) {
1241
1242
1243
1244
1245 Thread.interrupted();
1246 }
1247
1248
1249
1250 if (inRedirect != null) {
1251 inRedirect.close();
1252 }
1253 try {
1254 process.getErrorStream().close();
1255 } catch (IOException e) {
1256 ioException = ioException != null ? ioException : e;
1257 }
1258 try {
1259 process.getInputStream().close();
1260 } catch (IOException e) {
1261 ioException = ioException != null ? ioException : e;
1262 }
1263 try {
1264 process.getOutputStream().close();
1265 } catch (IOException e) {
1266 ioException = ioException != null ? ioException : e;
1267 }
1268 process.destroy();
1269 }
1270 }
1271
1272 throw ioException;
1273 }
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
1289 boolean hasShutdown = true;
1290 pool.shutdown();
1291 try {
1292
1293 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
1294 pool.shutdownNow();
1295
1296 if (!pool.awaitTermination(60, TimeUnit.SECONDS))
1297 hasShutdown = false;
1298 }
1299 } catch (InterruptedException ie) {
1300
1301 pool.shutdownNow();
1302
1303 Thread.currentThread().interrupt();
1304 hasShutdown = false;
1305 }
1306 return hasShutdown;
1307 }
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321 public abstract ProcessBuilder runInShell(String cmd, String[] args);
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335 public ExecutionResult execute(ProcessBuilder pb, InputStream in)
1336 throws IOException, InterruptedException {
1337 try (TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
1338 TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024,
1339 1024 * 1024)) {
1340 int rc = runProcess(pb, stdout, stderr, in);
1341 return new ExecutionResult(stdout, stderr, rc);
1342 }
1343 }
1344
1345 private static class Holder<V> {
1346 final V value;
1347
1348 Holder(V value) {
1349 this.value = value;
1350 }
1351 }
1352
1353
1354
1355
1356
1357
1358 public static class Attributes {
1359
1360
1361
1362
1363 public boolean isDirectory() {
1364 return isDirectory;
1365 }
1366
1367
1368
1369
1370 public boolean isExecutable() {
1371 return isExecutable;
1372 }
1373
1374
1375
1376
1377 public boolean isSymbolicLink() {
1378 return isSymbolicLink;
1379 }
1380
1381
1382
1383
1384 public boolean isRegularFile() {
1385 return isRegularFile;
1386 }
1387
1388
1389
1390
1391 public long getCreationTime() {
1392 return creationTime;
1393 }
1394
1395
1396
1397
1398
1399 public long getLastModifiedTime() {
1400 return lastModifiedTime;
1401 }
1402
1403 private final boolean isDirectory;
1404
1405 private final boolean isSymbolicLink;
1406
1407 private final boolean isRegularFile;
1408
1409 private final long creationTime;
1410
1411 private final long lastModifiedTime;
1412
1413 private final boolean isExecutable;
1414
1415 private final File file;
1416
1417 private final boolean exists;
1418
1419
1420
1421
1422 protected long length = -1;
1423
1424 final FS fs;
1425
1426 Attributes(FS fs, File file, boolean exists, boolean isDirectory,
1427 boolean isExecutable, boolean isSymbolicLink,
1428 boolean isRegularFile, long creationTime,
1429 long lastModifiedTime, long length) {
1430 this.fs = fs;
1431 this.file = file;
1432 this.exists = exists;
1433 this.isDirectory = isDirectory;
1434 this.isExecutable = isExecutable;
1435 this.isSymbolicLink = isSymbolicLink;
1436 this.isRegularFile = isRegularFile;
1437 this.creationTime = creationTime;
1438 this.lastModifiedTime = lastModifiedTime;
1439 this.length = length;
1440 }
1441
1442
1443
1444
1445
1446
1447
1448
1449 public Attributes(File path, FS fs) {
1450 this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
1451 }
1452
1453
1454
1455
1456 public long getLength() {
1457 if (length == -1)
1458 return length = file.length();
1459 return length;
1460 }
1461
1462
1463
1464
1465 public String getName() {
1466 return file.getName();
1467 }
1468
1469
1470
1471
1472 public File getFile() {
1473 return file;
1474 }
1475
1476 boolean exists() {
1477 return exists;
1478 }
1479 }
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489 public Attributes getAttributes(File path) {
1490 boolean isDirectory = isDirectory(path);
1491 boolean isFile = !isDirectory && path.isFile();
1492 assert path.exists() == isDirectory || isFile;
1493 boolean exists = isDirectory || isFile;
1494 boolean canExecute = exists && !isDirectory && canExecute(path);
1495 boolean isSymlink = false;
1496 long lastModified = exists ? path.lastModified() : 0L;
1497 long createTime = 0L;
1498 return new Attributes(this, path, exists, isDirectory, canExecute,
1499 isSymlink, isFile, createTime, lastModified, -1);
1500 }
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510 public File normalize(File file) {
1511 return file;
1512 }
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522 public String normalize(String name) {
1523 return name;
1524 }
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538 private static class StreamGobbler implements Runnable {
1539 private InputStream in;
1540
1541 private OutputStream out;
1542
1543 public StreamGobbler(InputStream stream, OutputStream output) {
1544 this.in = stream;
1545 this.out = output;
1546 }
1547
1548 @Override
1549 public void run() {
1550 try {
1551 copy();
1552 } catch (IOException e) {
1553
1554 }
1555 }
1556
1557 void copy() throws IOException {
1558 boolean writeFailure = false;
1559 byte buffer[] = new byte[4096];
1560 int readBytes;
1561 while ((readBytes = in.read(buffer)) != -1) {
1562
1563
1564
1565 if (!writeFailure && out != null) {
1566 try {
1567 out.write(buffer, 0, readBytes);
1568 out.flush();
1569 } catch (IOException e) {
1570 writeFailure = true;
1571 }
1572 }
1573 }
1574 }
1575 }
1576 }