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