1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.util;
14
15 import static java.nio.charset.StandardCharsets.UTF_8;
16
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.io.IOException;
20 import java.nio.channels.FileChannel;
21 import java.nio.file.AtomicMoveNotSupportedException;
22 import java.nio.file.CopyOption;
23 import java.nio.file.Files;
24 import java.nio.file.InvalidPathException;
25 import java.nio.file.LinkOption;
26 import java.nio.file.NoSuchFileException;
27 import java.nio.file.Path;
28 import java.nio.file.StandardCopyOption;
29 import java.nio.file.StandardOpenOption;
30 import java.nio.file.attribute.BasicFileAttributeView;
31 import java.nio.file.attribute.BasicFileAttributes;
32 import java.nio.file.attribute.FileTime;
33 import java.nio.file.attribute.PosixFileAttributeView;
34 import java.nio.file.attribute.PosixFileAttributes;
35 import java.nio.file.attribute.PosixFilePermission;
36 import java.text.MessageFormat;
37 import java.text.Normalizer;
38 import java.text.Normalizer.Form;
39 import java.time.Instant;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.Random;
44 import java.util.regex.Pattern;
45
46 import org.eclipse.jgit.internal.JGitText;
47 import org.eclipse.jgit.lib.Constants;
48 import org.eclipse.jgit.util.FS.Attributes;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52
53
54
55 public class FileUtils {
56 private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
57
58 private static final Random RNG = new Random();
59
60
61
62
63 public static final int NONE = 0;
64
65
66
67
68 public static final int RECURSIVE = 1;
69
70
71
72
73 public static final int RETRY = 2;
74
75
76
77
78 public static final int SKIP_MISSING = 4;
79
80
81
82
83
84 public static final int IGNORE_ERRORS = 8;
85
86
87
88
89
90
91
92 public static final int EMPTY_DIRECTORIES_ONLY = 16;
93
94
95
96
97
98
99
100
101
102
103
104
105 public static Path toPath(File f) throws IOException {
106 try {
107 return f.toPath();
108 } catch (InvalidPathException ex) {
109 throw new IOException(ex);
110 }
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124 public static void delete(File f) throws IOException {
125 delete(f, NONE);
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 public static void delete(File f, int options) throws IOException {
146 FS fs = FS.DETECTED;
147 if ((options & SKIP_MISSING) != 0 && !fs.exists(f))
148 return;
149
150 if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) {
151 final File[] items = f.listFiles();
152 if (items != null) {
153 List<File> files = new ArrayList<>();
154 List<File> dirs = new ArrayList<>();
155 for (File c : items)
156 if (c.isFile())
157 files.add(c);
158 else
159 dirs.add(c);
160
161
162
163 for (File file : files)
164 delete(file, options);
165 for (File d : dirs)
166 delete(d, options);
167 }
168 }
169
170 boolean delete = false;
171 if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
172 if (f.isDirectory()) {
173 delete = true;
174 } else if ((options & IGNORE_ERRORS) == 0) {
175 throw new IOException(MessageFormat.format(
176 JGitText.get().deleteFileFailed, f.getAbsolutePath()));
177 }
178 } else {
179 delete = true;
180 }
181
182 if (delete) {
183 Throwable t = null;
184 Path p = f.toPath();
185 try {
186 Files.delete(p);
187 return;
188 } catch (FileNotFoundException e) {
189 if ((options & (SKIP_MISSING | IGNORE_ERRORS)) == 0) {
190 throw new IOException(MessageFormat.format(
191 JGitText.get().deleteFileFailed,
192 f.getAbsolutePath()), e);
193 }
194 return;
195 } catch (IOException e) {
196 t = e;
197 }
198 if ((options & RETRY) != 0) {
199 for (int i = 1; i < 10; i++) {
200 try {
201 Thread.sleep(100);
202 } catch (InterruptedException ex) {
203
204 }
205 try {
206 Files.deleteIfExists(p);
207 return;
208 } catch (IOException e) {
209 t = e;
210 }
211 }
212 }
213 if ((options & IGNORE_ERRORS) == 0) {
214 throw new IOException(MessageFormat.format(
215 JGitText.get().deleteFileFailed, f.getAbsolutePath()),
216 t);
217 }
218 }
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 public static void rename(File src, File dst)
244 throws IOException {
245 rename(src, dst, StandardCopyOption.REPLACE_EXISTING);
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 public static void rename(final File src, final File dst,
277 CopyOption... options)
278 throws AtomicMoveNotSupportedException, IOException {
279 int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
280 while (--attempts >= 0) {
281 try {
282 Files.move(toPath(src), toPath(dst), options);
283 return;
284 } catch (AtomicMoveNotSupportedException e) {
285 throw e;
286 } catch (IOException e) {
287 try {
288 if (!dst.delete()) {
289 delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
290 }
291
292 Files.move(toPath(src), toPath(dst), options);
293 return;
294 } catch (IOException e2) {
295
296 }
297 }
298 try {
299 Thread.sleep(100);
300 } catch (InterruptedException e) {
301 throw new IOException(
302 MessageFormat.format(JGitText.get().renameFileFailed,
303 src.getAbsolutePath(), dst.getAbsolutePath()),
304 e);
305 }
306 }
307 throw new IOException(
308 MessageFormat.format(JGitText.get().renameFileFailed,
309 src.getAbsolutePath(), dst.getAbsolutePath()));
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324 public static void mkdir(File d)
325 throws IOException {
326 mkdir(d, false);
327 }
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public static void mkdir(File d, boolean skipExisting)
345 throws IOException {
346 if (!d.mkdir()) {
347 if (skipExisting && d.isDirectory())
348 return;
349 throw new IOException(MessageFormat.format(
350 JGitText.get().mkDirFailed, d.getAbsolutePath()));
351 }
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 public static void mkdirs(File d) throws IOException {
370 mkdirs(d, false);
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391 public static void mkdirs(File d, boolean skipExisting)
392 throws IOException {
393 if (!d.mkdirs()) {
394 if (skipExisting && d.isDirectory())
395 return;
396 throw new IOException(MessageFormat.format(
397 JGitText.get().mkDirsFailed, d.getAbsolutePath()));
398 }
399 }
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public static void createNewFile(File f) throws IOException {
418 if (!f.createNewFile())
419 throw new IOException(MessageFormat.format(
420 JGitText.get().createNewFileFailed, f));
421 }
422
423
424
425
426
427
428
429
430
431
432
433
434 public static Path createSymLink(File path, String target)
435 throws IOException {
436 Path nioPath = toPath(path);
437 if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
438 BasicFileAttributes attrs = Files.readAttributes(nioPath,
439 BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
440 if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
441 delete(path);
442 } else {
443 delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
444 }
445 }
446 if (SystemReader.getInstance().isWindows()) {
447 target = target.replace('/', '\\');
448 }
449 Path nioTarget = toPath(new File(target));
450 return Files.createSymbolicLink(nioPath, nioTarget);
451 }
452
453
454
455
456
457
458
459
460
461
462 public static String readSymLink(File path) throws IOException {
463 Path nioPath = toPath(path);
464 Path target = Files.readSymbolicLink(nioPath);
465 String targetString = target.toString();
466 if (SystemReader.getInstance().isWindows()) {
467 targetString = targetString.replace('\\', '/');
468 } else if (SystemReader.getInstance().isMacOS()) {
469 targetString = Normalizer.normalize(targetString, Form.NFC);
470 }
471 return targetString;
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487 public static File createTempDir(String prefix, String suffix, File dir)
488 throws IOException {
489 final int RETRIES = 1;
490 for (int i = 0; i < RETRIES; i++) {
491 File tmp = File.createTempFile(prefix, suffix, dir);
492 if (!tmp.delete())
493 continue;
494 if (!tmp.mkdir())
495 continue;
496 return tmp;
497 }
498 throw new IOException(JGitText.get().cannotCreateTempDir);
499 }
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516 public static String relativizeNativePath(String base, String other) {
517 return FS.DETECTED.relativize(base, other);
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535 public static String relativizeGitPath(String base, String other) {
536 return relativizePath(base, other, "/", false);
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571 public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
572 if (base.equals(other))
573 return "";
574
575 final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
576 final String[] otherSegments = other.split(Pattern
577 .quote(dirSeparator));
578
579 int commonPrefix = 0;
580 while (commonPrefix < baseSegments.length
581 && commonPrefix < otherSegments.length) {
582 if (caseSensitive
583 && baseSegments[commonPrefix]
584 .equals(otherSegments[commonPrefix]))
585 commonPrefix++;
586 else if (!caseSensitive
587 && baseSegments[commonPrefix]
588 .equalsIgnoreCase(otherSegments[commonPrefix]))
589 commonPrefix++;
590 else
591 break;
592 }
593
594 final StringBuilder builder = new StringBuilder();
595 for (int i = commonPrefix; i < baseSegments.length; i++)
596 builder.append("..").append(dirSeparator);
597 for (int i = commonPrefix; i < otherSegments.length; i++) {
598 builder.append(otherSegments[i]);
599 if (i < otherSegments.length - 1)
600 builder.append(dirSeparator);
601 }
602 return builder.toString();
603 }
604
605
606
607
608
609
610
611
612
613 public static boolean isStaleFileHandle(IOException ioe) {
614 String msg = ioe.getMessage();
615 return msg != null
616 && msg.toLowerCase(Locale.ROOT)
617 .matches("stale .*file .*handle");
618 }
619
620
621
622
623
624
625
626
627
628
629
630 public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
631 while (throwable != null) {
632 if (throwable instanceof IOException
633 && isStaleFileHandle((IOException) throwable)) {
634 return true;
635 }
636 throwable = throwable.getCause();
637 }
638 return false;
639 }
640
641
642
643
644
645 static boolean isSymlink(File file) {
646 return Files.isSymbolicLink(file.toPath());
647 }
648
649
650
651
652
653
654
655
656
657 @Deprecated
658 static long lastModified(File file) throws IOException {
659 return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
660 .toMillis();
661 }
662
663
664
665
666
667
668 static Instant lastModifiedInstant(Path path) {
669 try {
670 return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
671 .toInstant();
672 } catch (NoSuchFileException e) {
673 LOG.debug(
674 "Cannot read lastModifiedInstant since path {} does not exist",
675 path);
676 return Instant.EPOCH;
677 } catch (IOException e) {
678 LOG.error(MessageFormat
679 .format(JGitText.get().readLastModifiedFailed, path), e);
680 return Instant.ofEpochMilli(path.toFile().lastModified());
681 }
682 }
683
684
685
686
687
688
689
690
691
692
693 static BasicFileAttributes fileAttributes(File file) throws IOException {
694 return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
695 }
696
697
698
699
700
701
702 @Deprecated
703 static void setLastModified(File file, long time) throws IOException {
704 Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
705 }
706
707
708
709
710
711
712 static void setLastModified(Path path, Instant time)
713 throws IOException {
714 Files.setLastModifiedTime(path, FileTime.from(time));
715 }
716
717
718
719
720
721
722 static boolean exists(File file) {
723 return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS);
724 }
725
726
727
728
729
730
731 static boolean isHidden(File file) throws IOException {
732 return Files.isHidden(toPath(file));
733 }
734
735
736
737
738
739
740
741
742
743
744
745 public static void setHidden(File file, boolean hidden) throws IOException {
746 Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden),
747 LinkOption.NOFOLLOW_LINKS);
748 }
749
750
751
752
753
754
755
756
757
758
759 public static long getLength(File file) throws IOException {
760 Path nioPath = toPath(file);
761 if (Files.isSymbolicLink(nioPath))
762 return Files.readSymbolicLink(nioPath).toString()
763 .getBytes(UTF_8).length;
764 return Files.size(nioPath);
765 }
766
767
768
769
770
771
772 static boolean isDirectory(File file) {
773 return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS);
774 }
775
776
777
778
779
780
781 static boolean isFile(File file) {
782 return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS);
783 }
784
785
786
787
788
789
790
791
792
793 public static boolean canExecute(File file) {
794 if (!isFile(file)) {
795 return false;
796 }
797 return Files.isExecutable(file.toPath());
798 }
799
800
801
802
803
804
805 static Attributes getFileAttributesBasic(FS fs, File file) {
806 try {
807 Path nioPath = toPath(file);
808 BasicFileAttributes readAttributes = nioPath
809 .getFileSystem()
810 .provider()
811 .getFileAttributeView(nioPath,
812 BasicFileAttributeView.class,
813 LinkOption.NOFOLLOW_LINKS).readAttributes();
814 Attributes attributes = new Attributes(fs, file,
815 true,
816 readAttributes.isDirectory(),
817 fs.supportsExecute() ? file.canExecute() : false,
818 readAttributes.isSymbolicLink(),
819 readAttributes.isRegularFile(),
820 readAttributes.creationTime().toMillis(),
821 readAttributes.lastModifiedTime().toInstant(),
822 readAttributes.isSymbolicLink() ? Constants
823 .encode(readSymLink(file)).length
824 : readAttributes.size());
825 return attributes;
826 } catch (IOException e) {
827 return new Attributes(file, fs);
828 }
829 }
830
831
832
833
834
835
836
837
838
839
840
841 public static Attributes getFileAttributesPosix(FS fs, File file) {
842 try {
843 Path nioPath = toPath(file);
844 PosixFileAttributes readAttributes = nioPath
845 .getFileSystem()
846 .provider()
847 .getFileAttributeView(nioPath,
848 PosixFileAttributeView.class,
849 LinkOption.NOFOLLOW_LINKS).readAttributes();
850 Attributes attributes = new Attributes(
851 fs,
852 file,
853 true,
854 readAttributes.isDirectory(),
855 readAttributes.permissions().contains(
856 PosixFilePermission.OWNER_EXECUTE),
857 readAttributes.isSymbolicLink(),
858 readAttributes.isRegularFile(),
859 readAttributes.creationTime().toMillis(),
860 readAttributes.lastModifiedTime().toInstant(),
861 readAttributes.size());
862 return attributes;
863 } catch (IOException e) {
864 return new Attributes(file, fs);
865 }
866 }
867
868
869
870
871
872
873
874
875
876
877 public static File normalize(File file) {
878 if (SystemReader.getInstance().isMacOS()) {
879
880
881 String normalized = Normalizer.normalize(file.getPath(),
882 Normalizer.Form.NFC);
883 return new File(normalized);
884 }
885 return file;
886 }
887
888
889
890
891
892
893
894
895
896 public static String normalize(String name) {
897 if (SystemReader.getInstance().isMacOS()) {
898 if (name == null)
899 return null;
900 return Normalizer.normalize(name, Normalizer.Form.NFC);
901 }
902 return name;
903 }
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918 public static File canonicalize(File file) {
919 if (file == null) {
920 return null;
921 }
922 try {
923 return file.getCanonicalFile();
924 } catch (IOException e) {
925 return file;
926 }
927 }
928
929
930
931
932
933
934
935
936
937 public static String pathToString(File file) {
938 final String path = file.getPath();
939 if (SystemReader.getInstance().isWindows()) {
940 return path.replace('\\', '/');
941 }
942 return path;
943 }
944
945
946
947
948
949
950
951
952
953 public static void touch(Path f) throws IOException {
954 try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
955 StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
956
957 }
958 Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
959 }
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976 public static long delay(long last, long min, long max) {
977 long r = Math.max(0, last * 3 - min);
978 if (r > 0) {
979 int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
980 r = RNG.nextInt(c);
981 }
982 return Math.max(Math.min(min + r, max), min);
983 }
984 }