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