-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFileUtils.java
More file actions
2044 lines (1767 loc) · 102 KB
/
Copy pathFileUtils.java
File metadata and controls
2044 lines (1767 loc) · 102 KB
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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
244
245
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
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
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package com.termux.shared.file;
import android.os.Build;
import android.system.Os;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.io.RecursiveDeleteOption;
import com.termux.shared.file.filesystem.FileType;
import com.termux.shared.file.filesystem.FileTypes;
import com.termux.shared.data.DataUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.errors.Errno;
import com.termux.shared.errors.Error;
import com.termux.shared.errors.FunctionErrno;
import org.apache.commons.io.filefilter.AgeFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.LinkOption;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
public class FileUtils {
/** Required file permissions for the executable file for app usage. Executable file must have read and execute permissions */
public static final String APP_EXECUTABLE_FILE_PERMISSIONS = "r-x"; // Default: "r-x"
/** Required file permissions for the working directory for app usage. Working directory must have read and write permissions.
* Execute permissions should be attempted to be set, but ignored if they are missing */
public static final String APP_WORKING_DIRECTORY_PERMISSIONS = "rwx"; // Default: "rwx"
private static final String LOG_TAG = "FileUtils";
/**
* Get canonical path.
*
* If path is already an absolute path, then it is used as is to get canonical path.
* If path is not an absolute path and {code prefixForNonAbsolutePath} is not {@code null}, then
* {code prefixForNonAbsolutePath} + "/" is prefixed before path before getting canonical path.
* If path is not an absolute path and {code prefixForNonAbsolutePath} is {@code null}, then
* "/" is prefixed before path before getting canonical path.
*
* If an exception is raised to get the canonical path, then absolute path is returned.
*
* @param path The {@code path} to convert.
* @param prefixForNonAbsolutePath Optional prefix path to prefix before non-absolute paths. This
* can be set to {@code null} if non-absolute paths should
* be prefixed with "/". The call to {@link File#getCanonicalPath()}
* will automatically do this anyways.
* @return Returns the {@code canonical path}.
*/
public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath) {
if (path == null) path = "";
String absolutePath;
// If path is already an absolute path
if (path.startsWith("/")) {
absolutePath = path;
} else {
if (prefixForNonAbsolutePath != null)
absolutePath = prefixForNonAbsolutePath + "/" + path;
else
absolutePath = "/" + path;
}
try {
return new File(absolutePath).getCanonicalPath();
} catch(Exception e) {
}
return absolutePath;
}
/**
* Removes one or more forward slashes "//" with single slash "/"
* Removes "./"
* Removes trailing forward slash "/"
*
* @param path The {@code path} to convert.
* @return Returns the {@code normalized path}.
*/
@Nullable
public static String normalizePath(String path) {
if (path == null) return null;
path = path.replaceAll("/+", "/");
path = path.replaceAll("\\./", "");
if (path.endsWith("/")) {
path = path.replaceAll("/+$", "");
}
return path;
}
/**
* Convert special characters `\/:*?"<>|` to underscore.
*
* @param fileName The name to sanitize.
* @param sanitizeWhitespaces If set to {@code true}, then white space characters ` \t\n` will be
* converted.
* @param toLower If set to {@code true}, then file name will be converted to lower case.
* @return Returns the {@code sanitized name}.
*/
public static String sanitizeFileName(String fileName, boolean sanitizeWhitespaces, boolean toLower) {
if (fileName == null) return null;
if (sanitizeWhitespaces)
fileName = fileName.replaceAll("[\\\\/:*?\"<>| \t\n]", "_");
else
fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
if (toLower)
return fileName.toLowerCase();
else
return fileName;
}
/**
* Determines whether path is in {@code dirPath}. The {@code dirPath} is not canonicalized and
* only normalized.
*
* @param path The {@code path} to check.
* @param dirPath The {@code directory path} to check in.
* @param ensureUnder If set to {@code true}, then it will be ensured that {@code path} is
* under the directory and does not equal it.
* @return Returns {@code true} if path in {@code dirPath}, otherwise returns {@code false}.
*/
public static boolean isPathInDirPath(String path, final String dirPath, final boolean ensureUnder) {
return isPathInDirPaths(path, Collections.singletonList(dirPath), ensureUnder);
}
/**
* Determines whether path is in one of the {@code dirPaths}. The {@code dirPaths} are not
* canonicalized and only normalized.
*
* @param path The {@code path} to check.
* @param dirPaths The {@code directory paths} to check in.
* @param ensureUnder If set to {@code true}, then it will be ensured that {@code path} is
* under the directories and does not equal it.
* @return Returns {@code true} if path in {@code dirPaths}, otherwise returns {@code false}.
*/
public static boolean isPathInDirPaths(String path, final List<String> dirPaths, final boolean ensureUnder) {
if (path == null || path.isEmpty() || dirPaths == null || dirPaths.size() < 1) return false;
try {
path = new File(path).getCanonicalPath();
} catch(Exception e) {
return false;
}
boolean isPathInDirPaths;
for (String dirPath : dirPaths) {
String normalizedDirPath = normalizePath(dirPath);
if (ensureUnder)
isPathInDirPaths = !path.equals(normalizedDirPath) && path.startsWith(normalizedDirPath + "/");
else
isPathInDirPaths = path.startsWith(normalizedDirPath + "/");
if (isPathInDirPaths) return true;
}
return false;
}
/**
* Validate that directory is empty or contains only files in {@code ignoredSubFilePaths}.
*
* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
* is not considered empty.
*
* @param label The optional label for directory to check. This can optionally be {@code null}.
* @param filePath The {@code path} for directory to check.
* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
* Validation is done for the paths.
* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
* error if file to be checked doesn't exist.
* @return Returns {@code null} if directory is empty or contains only files in {@code ignoredSubFilePaths}.
* Returns {@code FileUtilsErrno#ERRNO_NON_EMPTY_DIRECTORY_FILE} if a file was found that did not
* exist in the {@code ignoredSubFilePaths}, otherwise returns an appropriate {@code error} if
* checking was not successful.
*/
public static Error validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String label, String filePath,
final List<String> ignoredSubFilePaths,
final boolean ignoreNonExistentFile) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "isDirectoryFileEmptyOrOnlyContainsSpecificFiles");
try {
File file = new File(filePath);
FileType fileType = getFileType(filePath, false);
// If file exists but not a directory file
if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
}
// If file does not exist
if (fileType == FileType.NO_EXIST) {
// If checking is to be ignored if file does not exist
if (ignoreNonExistentFile)
return null;
else {
label += "directory to check if is empty or only contains specific files";
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
}
}
File[] subFiles = file.listFiles();
if (subFiles == null || subFiles.length == 0)
return null;
// If sub files exists but no file should be ignored
if (ignoredSubFilePaths == null || ignoredSubFilePaths.size() == 0)
return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
// If a sub file does not exist in ignored file path
if (nonIgnoredSubFileExists(subFiles, ignoredSubFilePaths)) {
return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
}
} catch (Exception e) {
return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EMPTY_OR_ONLY_CONTAINS_SPECIFIC_FILES_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, e.getMessage());
}
return null;
}
/**
* Check if {@code subFiles} contains contains a file not in {@code ignoredSubFilePaths}.
*
* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
* is not considered empty.
*
* This function should ideally not be called by itself but through
* {@link #validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String, String, List, boolean)}.
*
* @param subFiles The list of files of a directory to check.
* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
* Validation is done for the paths.
* @return Returns {@code true} if a file was found that did not exist in the {@code ignoredSubFilePaths},
* otherwise {@code false}.
*/
public static boolean nonIgnoredSubFileExists(File[] subFiles, @NonNull List<String> ignoredSubFilePaths) {
if (subFiles == null || subFiles.length == 0) return false;
String subFilePath;
for (File subFile : subFiles) {
subFilePath = subFile.getAbsolutePath();
// If sub file does not exist in ignored sub file paths
if (!ignoredSubFilePaths.contains(subFilePath)) {
boolean isParentPath = false;
for (String ignoredSubFilePath : ignoredSubFilePaths) {
if (ignoredSubFilePath.startsWith(subFilePath + "/") && fileExists(ignoredSubFilePath, false)) {
isParentPath = true;
break;
}
}
// If sub file is not a parent of any existing ignored sub file paths
if (!isParentPath) {
return true;
}
}
if (getFileType(subFilePath, false) == FileType.DIRECTORY) {
// If non ignored sub file found, then early exit, otherwise continue looking
if (nonIgnoredSubFileExists(subFile.listFiles(), ignoredSubFilePaths))
return true;
}
}
return false;
}
/**
* Checks whether a regular file exists at {@code filePath}.
*
* @param filePath The {@code path} for regular file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding if file exists. Check {@link #getFileType(String, boolean)}
* for details.
* @return Returns {@code true} if regular file exists, otherwise {@code false}.
*/
public static boolean regularFileExists(final String filePath, final boolean followLinks) {
return getFileType(filePath, followLinks) == FileType.REGULAR;
}
/**
* Checks whether a directory file exists at {@code filePath}.
*
* @param filePath The {@code path} for directory file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding if file exists. Check {@link #getFileType(String, boolean)}
* for details.
* @return Returns {@code true} if directory file exists, otherwise {@code false}.
*/
public static boolean directoryFileExists(final String filePath, final boolean followLinks) {
return getFileType(filePath, followLinks) == FileType.DIRECTORY;
}
/**
* Checks whether a symlink file exists at {@code filePath}.
*
* @param filePath The {@code path} for symlink file to check.
* @return Returns {@code true} if symlink file exists, otherwise {@code false}.
*/
public static boolean symlinkFileExists(final String filePath) {
return getFileType(filePath, false) == FileType.SYMLINK;
}
/**
* Checks whether a regular or directory file exists at {@code filePath}.
*
* @param filePath The {@code path} for regular file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding if file exists. Check {@link #getFileType(String, boolean)}
* for details.
* @return Returns {@code true} if regular or directory file exists, otherwise {@code false}.
*/
public static boolean regularOrDirectoryFileExists(final String filePath, final boolean followLinks) {
FileType fileType = getFileType(filePath, followLinks);
return fileType == FileType.REGULAR || fileType == FileType.DIRECTORY;
}
/**
* Checks whether any file exists at {@code filePath}.
*
* @param filePath The {@code path} for file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding if file exists. Check {@link #getFileType(String, boolean)}
* for details.
* @return Returns {@code true} if file exists, otherwise {@code false}.
*/
public static boolean fileExists(final String filePath, final boolean followLinks) {
return getFileType(filePath, followLinks) != FileType.NO_EXIST;
}
/**
* Get the type of file that exists at {@code filePath}.
*
* This function is a wrapper for
* {@link FileTypes#getFileType(String, boolean)}
*
* @param filePath The {@code path} for file to check.
* @param followLinks The {@code boolean} that decides if symlinks will be followed while
* finding type. If set to {@code true}, then type of symlink target will
* be returned if file at {@code filePath} is a symlink. If set to
* {@code false}, then type of file at {@code filePath} itself will be
* returned.
* @return Returns the {@link FileType} of file.
*/
@NonNull
public static FileType getFileType(final String filePath, final boolean followLinks) {
return FileTypes.getFileType(filePath, followLinks);
}
/**
* Validate the existence and permissions of regular file at path.
*
* If the {@code parentDirPath} is not {@code null}, then setting of missing permissions will
* only be done if {@code path} is under {@code parentDirPath}.
*
* @param label The optional label for the regular file. This can optionally be {@code null}.
* @param filePath The {@code path} for file to validate. Symlinks will not be followed.
* @param parentDirPath The optional {@code parent directory path} to restrict operations to.
* This can optionally be {@code null}. It is not canonicalized and only normalized.
* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
* @param setPermissions The {@code boolean} that decides if permissions are to be
* automatically set defined by {@code permissionsToCheck}.
* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
* are to be set or if they should be overridden.
* @param ignoreErrorsIfPathIsUnderParentDirPath The {@code boolean} that decides if permission
* errors are to be ignored if path is under
* {@code parentDirPath}.
* @return Returns the {@code error} if path is not a regular file, or validating permissions
* failed, otherwise {@code null}.
*/
public static Error validateRegularFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath,
final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly,
final boolean ignoreErrorsIfPathIsUnderParentDirPath) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "regular file path", "validateRegularFileExistenceAndPermissions");
try {
FileType fileType = getFileType(filePath, false);
// If file exists but not a regular file
if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
}
boolean isPathUnderParentDirPath = false;
if (parentDirPath != null) {
// The path can only be under parent directory path
isPathUnderParentDirPath = isPathInDirPath(filePath, parentDirPath, true);
}
// If setPermissions is enabled and path is a regular file
if (setPermissions && permissionsToCheck != null && fileType == FileType.REGULAR) {
// If there is not parentDirPath restriction or path is under parentDirPath
if (parentDirPath == null || (isPathUnderParentDirPath && getFileType(parentDirPath, false) == FileType.DIRECTORY)) {
if (setMissingPermissionsOnly)
setMissingFilePermissions(label + "file", filePath, permissionsToCheck);
else
setFilePermissions(label + "file", filePath, permissionsToCheck);
}
}
// If path is not a regular file
// Regular files cannot be automatically created so we do not ignore if missing
if (fileType != FileType.REGULAR) {
label += "regular file";
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
}
// If there is not parentDirPath restriction or path is not under parentDirPath or
// if permission errors must not be ignored for paths under parentDirPath
if (parentDirPath == null || !isPathUnderParentDirPath || !ignoreErrorsIfPathIsUnderParentDirPath) {
if (permissionsToCheck != null) {
// Check if permissions are missing
return checkMissingFilePermissions(label + "regular", filePath, permissionsToCheck, false);
}
}
} catch (Exception e) {
return FileUtilsErrno.ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
}
return null;
}
/**
* Validate the existence and permissions of directory file at path.
*
* If the {@code parentDirPath} is not {@code null}, then creation of missing directory and
* setting of missing permissions will only be done if {@code path} is under
* {@code parentDirPath} or equals {@code parentDirPath}.
*
* @param label The optional label for the directory file. This can optionally be {@code null}.
* @param filePath The {@code path} for file to validate or create. Symlinks will not be followed.
* @param parentDirPath The optional {@code parent directory path} to restrict operations to.
* This can optionally be {@code null}. It is not canonicalized and only normalized.
* @param createDirectoryIfMissing The {@code boolean} that decides if directory file
* should be created if its missing.
* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
* @param setPermissions The {@code boolean} that decides if permissions are to be
* automatically set defined by {@code permissionsToCheck}.
* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
* are to be set or if they should be overridden.
* @param ignoreErrorsIfPathIsInParentDirPath The {@code boolean} that decides if existence
* and permission errors are to be ignored if path is
* in {@code parentDirPath}.
* @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission
* error is to be ignored. This allows making an attempt to set
* executable permissions, but ignoring if it fails.
* @return Returns the {@code error} if path is not a directory file, failed to create it,
* or validating permissions failed, otherwise {@code null}.
*/
public static Error validateDirectoryFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath, final boolean createDirectoryIfMissing,
final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly,
final boolean ignoreErrorsIfPathIsInParentDirPath, final boolean ignoreIfNotExecutable) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "directory file path", "validateDirectoryExistenceAndPermissions");
try {
File file = new File(filePath);
FileType fileType = getFileType(filePath, false);
// If file exists but not a directory file
if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
}
boolean isPathInParentDirPath = false;
if (parentDirPath != null) {
// The path can be equal to parent directory path or under it
isPathInParentDirPath = isPathInDirPath(filePath, parentDirPath, false);
}
if (createDirectoryIfMissing || setPermissions) {
// If there is not parentDirPath restriction or path is in parentDirPath
if (parentDirPath == null || (isPathInParentDirPath && getFileType(parentDirPath, false) == FileType.DIRECTORY)) {
// If createDirectoryIfMissing is enabled and no file exists at path, then create directory
if (createDirectoryIfMissing && fileType == FileType.NO_EXIST) {
Logger.logVerbose(LOG_TAG, "Creating " + label + "directory file at path \"" + filePath + "\"");
// Create directory and update fileType if successful, otherwise return with error
// It "might" be possible that mkdirs returns false even though directory was created
boolean result = file.mkdirs();
fileType = getFileType(filePath, false);
if (!result && fileType != FileType.DIRECTORY)
return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "directory file", filePath);
}
// If setPermissions is enabled and path is a directory
if (setPermissions && permissionsToCheck != null && fileType == FileType.DIRECTORY) {
if (setMissingPermissionsOnly)
setMissingFilePermissions(label + "directory", filePath, permissionsToCheck);
else
setFilePermissions(label + "directory", filePath, permissionsToCheck);
}
}
}
// If there is not parentDirPath restriction or path is not in parentDirPath or
// if existence or permission errors must not be ignored for paths in parentDirPath
if (parentDirPath == null || !isPathInParentDirPath || !ignoreErrorsIfPathIsInParentDirPath) {
// If path is not a directory
// Directories can be automatically created so we can ignore if missing with above check
if (fileType != FileType.DIRECTORY) {
label += "directory";
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
}
if (permissionsToCheck != null) {
// Check if permissions are missing
return checkMissingFilePermissions(label + "directory", filePath, permissionsToCheck, ignoreIfNotExecutable);
}
}
} catch (Exception e) {
return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "directory file", filePath, e.getMessage());
}
return null;
}
/**
* Create a regular file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param filePath The {@code path} for regular file to create.
* @return Returns the {@code error} if path is not a regular file or failed to create it,
* otherwise {@code null}.
*/
public static Error createRegularFile(final String filePath) {
return createRegularFile(null, filePath);
}
/**
* Create a regular file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param label The optional label for the regular file. This can optionally be {@code null}.
* @param filePath The {@code path} for regular file to create.
* @return Returns the {@code error} if path is not a regular file or failed to create it,
* otherwise {@code null}.
*/
public static Error createRegularFile(final String label, final String filePath) {
return createRegularFile(label, filePath,
null, false, false);
}
/**
* Create a regular file at path.
*
* This function is a wrapper for
* {@link #validateRegularFileExistenceAndPermissions(String, String, String, String, boolean, boolean, boolean)}.
*
* @param label The optional label for the regular file. This can optionally be {@code null}.
* @param filePath The {@code path} for regular file to create.
* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
* @param setPermissions The {@code boolean} that decides if permissions are to be
* automatically set defined by {@code permissionsToCheck}.
* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
* are to be set or if they should be overridden.
* @return Returns the {@code error} if path is not a regular file, failed to create it,
* or validating permissions failed, otherwise {@code null}.
*/
public static Error createRegularFile(String label, final String filePath,
final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createRegularFile");
Error error;
File file = new File(filePath);
FileType fileType = getFileType(filePath, false);
// If file exists but not a regular file
if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
}
// If regular file already exists
if (fileType == FileType.REGULAR) {
return null;
}
// Create the file parent directory
error = createParentDirectoryFile(label + "regular file parent", filePath);
if (error != null)
return error;
try {
Logger.logVerbose(LOG_TAG, "Creating " + label + "regular file at path \"" + filePath + "\"");
if (!file.createNewFile())
return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "regular file", filePath);
} catch (Exception e) {
return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "regular file", filePath, e.getMessage());
}
return validateRegularFileExistenceAndPermissions(label, filePath,
null,
permissionsToCheck, setPermissions, setMissingPermissionsOnly,
false);
}
/**
* Create parent directory of file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param label The optional label for the parent directory file. This can optionally be {@code null}.
* @param filePath The {@code path} for file whose parent needs to be created.
* @return Returns the {@code error} if parent path is not a directory file or failed to create it,
* otherwise {@code null}.
*/
public static Error createParentDirectoryFile(final String label, final String filePath) {
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createParentDirectoryFile");
File file = new File(filePath);
String fileParentPath = file.getParent();
if (fileParentPath != null)
return createDirectoryFile(label, fileParentPath,
null, false, false);
else
return null;
}
/**
* Create a directory file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param filePath The {@code path} for directory file to create.
* @return Returns the {@code error} if path is not a directory file or failed to create it,
* otherwise {@code null}.
*/
public static Error createDirectoryFile(final String filePath) {
return createDirectoryFile(null, filePath);
}
/**
* Create a directory file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param label The optional label for the directory file. This can optionally be {@code null}.
* @param filePath The {@code path} for directory file to create.
* @return Returns the {@code error} if path is not a directory file or failed to create it,
* otherwise {@code null}.
*/
public static Error createDirectoryFile(final String label, final String filePath) {
return createDirectoryFile(label, filePath,
null, false, false);
}
/**
* Create a directory file at path.
*
* This function is a wrapper for
* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
*
* @param label The optional label for the directory file. This can optionally be {@code null}.
* @param filePath The {@code path} for directory file to create.
* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
* @param setPermissions The {@code boolean} that decides if permissions are to be
* automatically set defined by {@code permissionsToCheck}.
* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
* are to be set or if they should be overridden.
* @return Returns the {@code error} if path is not a directory file, failed to create it,
* or validating permissions failed, otherwise {@code null}.
*/
public static Error createDirectoryFile(final String label, final String filePath,
final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) {
return validateDirectoryFileExistenceAndPermissions(label, filePath,
null, true,
permissionsToCheck, setPermissions, setMissingPermissionsOnly,
false, false);
}
/**
* Create a symlink file at path.
*
* This function is a wrapper for
* {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}.
*
* Dangling symlinks will be allowed.
* Symlink destination will be overwritten if it already exists but only if its a symlink.
*
* @param targetFilePath The {@code path} TO which the symlink file will be created.
* @param destFilePath The {@code path} AT which the symlink file will be created.
* @return Returns the {@code error} if path is not a symlink file, failed to create it,
* otherwise {@code null}.
*/
public static Error createSymlinkFile(final String targetFilePath, final String destFilePath) {
return createSymlinkFile(null, targetFilePath, destFilePath,
true, true, true);
}
/**
* Create a symlink file at path.
*
* This function is a wrapper for
* {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}.
*
* Dangling symlinks will be allowed.
* Symlink destination will be overwritten if it already exists but only if its a symlink.
*
* @param label The optional label for the symlink file. This can optionally be {@code null}.
* @param targetFilePath The {@code path} TO which the symlink file will be created.
* @param destFilePath The {@code path} AT which the symlink file will be created.
* @return Returns the {@code error} if path is not a symlink file, failed to create it,
* otherwise {@code null}.
*/
public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath) {
return createSymlinkFile(label, targetFilePath, destFilePath,
true, true, true);
}
/**
* Create a symlink file at path.
*
* @param label The optional label for the symlink file. This can optionally be {@code null}.
* @param targetFilePath The {@code path} TO which the symlink file will be created.
* @param destFilePath The {@code path} AT which the symlink file will be created.
* @param allowDangling The {@code boolean} that decides if it should be considered an
* error if source file doesn't exist.
* @param overwrite The {@code boolean} that decides if destination file should be overwritten if
* it already exists. If set to {@code true}, then destination file will be
* deleted before symlink is created.
* @param overwriteOnlyIfDestIsASymlink The {@code boolean} that decides if overwrite should
* only be done if destination file is also a symlink.
* @return Returns the {@code error} if path is not a symlink file, failed to create it,
* or validating permissions failed, otherwise {@code null}.
*/
public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath,
final boolean allowDangling, final boolean overwrite, final boolean overwriteOnlyIfDestIsASymlink) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (targetFilePath == null || targetFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "target file path", "createSymlinkFile");
if (destFilePath == null || destFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "destination file path", "createSymlinkFile");
Error error;
try {
File destFile = new File(destFilePath);
String targetFileAbsolutePath = targetFilePath;
// If target path is relative instead of absolute
if (!targetFilePath.startsWith("/")) {
String destFileParentPath = destFile.getParent();
if (destFileParentPath != null)
targetFileAbsolutePath = destFileParentPath + "/" + targetFilePath;
}
FileType targetFileType = getFileType(targetFileAbsolutePath, false);
FileType destFileType = getFileType(destFilePath, false);
// If target file does not exist
if (targetFileType == FileType.NO_EXIST) {
// If dangling symlink should not be allowed, then return with error
if (!allowDangling) {
label += "symlink target file";
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, targetFileAbsolutePath).setLabel(label);
}
}
// If destination exists
if (destFileType != FileType.NO_EXIST) {
// If destination must not be overwritten
if (!overwrite) {
return null;
}
// If overwriteOnlyIfDestIsASymlink is enabled but destination file is not a symlink
if (overwriteOnlyIfDestIsASymlink && destFileType != FileType.SYMLINK)
return FileUtilsErrno.ERRNO_CANNOT_OVERWRITE_A_NON_SYMLINK_FILE_TYPE.getError(label + " file", destFilePath, targetFilePath, destFileType.getName());
// Delete the destination file
error = deleteFile(label + "symlink destination", destFilePath, true);
if (error != null)
return error;
} else {
// Create the destination file parent directory
error = createParentDirectoryFile(label + "symlink destination file parent", destFilePath);
if (error != null)
return error;
}
// create a symlink at destFilePath to targetFilePath
Logger.logVerbose(LOG_TAG, "Creating " + label + "symlink file at path \"" + destFilePath + "\" to \"" + targetFilePath + "\"");
Os.symlink(targetFilePath, destFilePath);
} catch (Exception e) {
return FileUtilsErrno.ERRNO_CREATING_SYMLINK_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "symlink file", destFilePath, targetFilePath, e.getMessage());
}
return null;
}
/**
* Copy a regular file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a regular
* file, otherwise an error will be returned.
*
* @param label The optional label for file to copy. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to copy.
* @param destFilePath The {@code destination path} for file to copy.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to copied doesn't exist.
* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
*/
public static Error copyRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
false, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(),
true, true);
}
/**
* Move a regular file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a regular
* file, otherwise an error will be returned.
*
* @param label The optional label for file to move. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to move.
* @param destFilePath The {@code destination path} for file to move.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to moved doesn't exist.
* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
*/
public static Error moveRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
true, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(),
true, true);
}
/**
* Copy a directory file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a directory
* file, otherwise an error will be returned.
*
* @param label The optional label for file to copy. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to copy.
* @param destFilePath The {@code destination path} for file to copy.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to copied doesn't exist.
* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
*/
public static Error copyDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
false, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(),
true, true);
}
/**
* Move a directory file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a directory
* file, otherwise an error will be returned.
*
* @param label The optional label for file to move. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to move.
* @param destFilePath The {@code destination path} for file to move.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to moved doesn't exist.
* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
*/
public static Error moveDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
true, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(),
true, true);
}
/**
* Copy a symlink file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a symlink
* file, otherwise an error will be returned.
*
* @param label The optional label for file to copy. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to copy.
* @param destFilePath The {@code destination path} for file to copy.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to copied doesn't exist.
* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
*/
public static Error copySymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
false, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(),
true, true);
}
/**
* Move a symlink file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its a symlink
* file, otherwise an error will be returned.
*
* @param label The optional label for file to move. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to move.
* @param destFilePath The {@code destination path} for file to move.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to moved doesn't exist.
* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
*/
public static Error moveSymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
true, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(),
true, true);
}
/**
* Copy a file from {@code sourceFilePath} to {@code destFilePath}.
*
* This function is a wrapper for
* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
*
* If destination file already exists, then it will be overwritten, but only if its the same file
* type as the source, otherwise an error will be returned.
*
* @param label The optional label for file to copy. This can optionally be {@code null}.
* @param srcFilePath The {@code source path} for file to copy.
* @param destFilePath The {@code destination path} for file to copy.
* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
* error if source file to copied doesn't exist.
* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
*/
public static Error copyFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
return copyOrMoveFile(label, srcFilePath, destFilePath,
false, ignoreNonExistentSrcFile, FileTypes.FILE_TYPE_NORMAL_FLAGS,
true, true);
}
/**
* Move a file from {@code sourceFilePath} to {@code destFilePath}.
*