termux-app
2044 строки · 102.1 Кб
1package com.termux.shared.file;
2
3import android.os.Build;
4import android.system.Os;
5
6import androidx.annotation.NonNull;
7import androidx.annotation.Nullable;
8
9import com.google.common.io.RecursiveDeleteOption;
10import com.termux.shared.file.filesystem.FileType;
11import com.termux.shared.file.filesystem.FileTypes;
12import com.termux.shared.data.DataUtils;
13import com.termux.shared.logger.Logger;
14import com.termux.shared.errors.Errno;
15import com.termux.shared.errors.Error;
16import com.termux.shared.errors.FunctionErrno;
17
18import org.apache.commons.io.filefilter.AgeFileFilter;
19import org.apache.commons.io.filefilter.IOFileFilter;
20
21import java.io.BufferedReader;
22import java.io.BufferedWriter;
23import java.io.Closeable;
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.InputStreamReader;
29import java.io.ObjectInputStream;
30import java.io.ObjectOutputStream;
31import java.io.OutputStreamWriter;
32import java.io.Serializable;
33import java.nio.charset.Charset;
34import java.nio.file.LinkOption;
35import java.nio.file.StandardCopyOption;
36import java.util.Arrays;
37import java.util.Calendar;
38import java.util.Collections;
39import java.util.Iterator;
40import java.util.List;
41import java.util.regex.Pattern;
42
43public class FileUtils {
44
45/** Required file permissions for the executable file for app usage. Executable file must have read and execute permissions */
46public static final String APP_EXECUTABLE_FILE_PERMISSIONS = "r-x"; // Default: "r-x"
47/** Required file permissions for the working directory for app usage. Working directory must have read and write permissions.
48* Execute permissions should be attempted to be set, but ignored if they are missing */
49public static final String APP_WORKING_DIRECTORY_PERMISSIONS = "rwx"; // Default: "rwx"
50
51private static final String LOG_TAG = "FileUtils";
52
53/**
54* Get canonical path.
55*
56* If path is already an absolute path, then it is used as is to get canonical path.
57* If path is not an absolute path and {code prefixForNonAbsolutePath} is not {@code null}, then
58* {code prefixForNonAbsolutePath} + "/" is prefixed before path before getting canonical path.
59* If path is not an absolute path and {code prefixForNonAbsolutePath} is {@code null}, then
60* "/" is prefixed before path before getting canonical path.
61*
62* If an exception is raised to get the canonical path, then absolute path is returned.
63*
64* @param path The {@code path} to convert.
65* @param prefixForNonAbsolutePath Optional prefix path to prefix before non-absolute paths. This
66* can be set to {@code null} if non-absolute paths should
67* be prefixed with "/". The call to {@link File#getCanonicalPath()}
68* will automatically do this anyways.
69* @return Returns the {@code canonical path}.
70*/
71public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath) {
72if (path == null) path = "";
73
74String absolutePath;
75
76// If path is already an absolute path
77if (path.startsWith("/")) {
78absolutePath = path;
79} else {
80if (prefixForNonAbsolutePath != null)
81absolutePath = prefixForNonAbsolutePath + "/" + path;
82else
83absolutePath = "/" + path;
84}
85
86try {
87return new File(absolutePath).getCanonicalPath();
88} catch(Exception e) {
89}
90
91return absolutePath;
92}
93
94/**
95* Removes one or more forward slashes "//" with single slash "/"
96* Removes "./"
97* Removes trailing forward slash "/"
98*
99* @param path The {@code path} to convert.
100* @return Returns the {@code normalized path}.
101*/
102@Nullable
103public static String normalizePath(String path) {
104if (path == null) return null;
105
106path = path.replaceAll("/+", "/");
107path = path.replaceAll("\\./", "");
108
109if (path.endsWith("/")) {
110path = path.replaceAll("/+$", "");
111}
112
113return path;
114}
115
116/**
117* Convert special characters `\/:*?"<>|` to underscore.
118*
119* @param fileName The name to sanitize.
120* @param sanitizeWhitespaces If set to {@code true}, then white space characters ` \t\n` will be
121* converted.
122* @param toLower If set to {@code true}, then file name will be converted to lower case.
123* @return Returns the {@code sanitized name}.
124*/
125public static String sanitizeFileName(String fileName, boolean sanitizeWhitespaces, boolean toLower) {
126if (fileName == null) return null;
127
128if (sanitizeWhitespaces)
129fileName = fileName.replaceAll("[\\\\/:*?\"<>| \t\n]", "_");
130else
131fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
132
133if (toLower)
134return fileName.toLowerCase();
135else
136return fileName;
137}
138
139/**
140* Determines whether path is in {@code dirPath}. The {@code dirPath} is not canonicalized and
141* only normalized.
142*
143* @param path The {@code path} to check.
144* @param dirPath The {@code directory path} to check in.
145* @param ensureUnder If set to {@code true}, then it will be ensured that {@code path} is
146* under the directory and does not equal it.
147* @return Returns {@code true} if path in {@code dirPath}, otherwise returns {@code false}.
148*/
149public static boolean isPathInDirPath(String path, final String dirPath, final boolean ensureUnder) {
150return isPathInDirPaths(path, Collections.singletonList(dirPath), ensureUnder);
151}
152
153/**
154* Determines whether path is in one of the {@code dirPaths}. The {@code dirPaths} are not
155* canonicalized and only normalized.
156*
157* @param path The {@code path} to check.
158* @param dirPaths The {@code directory paths} to check in.
159* @param ensureUnder If set to {@code true}, then it will be ensured that {@code path} is
160* under the directories and does not equal it.
161* @return Returns {@code true} if path in {@code dirPaths}, otherwise returns {@code false}.
162*/
163public static boolean isPathInDirPaths(String path, final List<String> dirPaths, final boolean ensureUnder) {
164if (path == null || path.isEmpty() || dirPaths == null || dirPaths.size() < 1) return false;
165
166try {
167path = new File(path).getCanonicalPath();
168} catch(Exception e) {
169return false;
170}
171
172boolean isPathInDirPaths;
173
174for (String dirPath : dirPaths) {
175String normalizedDirPath = normalizePath(dirPath);
176
177if (ensureUnder)
178isPathInDirPaths = !path.equals(normalizedDirPath) && path.startsWith(normalizedDirPath + "/");
179else
180isPathInDirPaths = path.startsWith(normalizedDirPath + "/");
181
182if (isPathInDirPaths) return true;
183}
184
185return false;
186}
187
188
189
190/**
191* Validate that directory is empty or contains only files in {@code ignoredSubFilePaths}.
192*
193* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
194* is not considered empty.
195*
196* @param label The optional label for directory to check. This can optionally be {@code null}.
197* @param filePath The {@code path} for directory to check.
198* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
199* Validation is done for the paths.
200* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
201* error if file to be checked doesn't exist.
202* @return Returns {@code null} if directory is empty or contains only files in {@code ignoredSubFilePaths}.
203* Returns {@code FileUtilsErrno#ERRNO_NON_EMPTY_DIRECTORY_FILE} if a file was found that did not
204* exist in the {@code ignoredSubFilePaths}, otherwise returns an appropriate {@code error} if
205* checking was not successful.
206*/
207public static Error validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String label, String filePath,
208final List<String> ignoredSubFilePaths,
209final boolean ignoreNonExistentFile) {
210label = (label == null || label.isEmpty() ? "" : label + " ");
211if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "isDirectoryFileEmptyOrOnlyContainsSpecificFiles");
212
213try {
214File file = new File(filePath);
215FileType fileType = getFileType(filePath, false);
216
217// If file exists but not a directory file
218if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
219return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
220}
221
222// If file does not exist
223if (fileType == FileType.NO_EXIST) {
224// If checking is to be ignored if file does not exist
225if (ignoreNonExistentFile)
226return null;
227else {
228label += "directory to check if is empty or only contains specific files";
229return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
230}
231}
232
233File[] subFiles = file.listFiles();
234if (subFiles == null || subFiles.length == 0)
235return null;
236
237// If sub files exists but no file should be ignored
238if (ignoredSubFilePaths == null || ignoredSubFilePaths.size() == 0)
239return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
240
241// If a sub file does not exist in ignored file path
242if (nonIgnoredSubFileExists(subFiles, ignoredSubFilePaths)) {
243return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
244}
245
246} catch (Exception e) {
247return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EMPTY_OR_ONLY_CONTAINS_SPECIFIC_FILES_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, e.getMessage());
248}
249
250return null;
251}
252
253/**
254* Check if {@code subFiles} contains contains a file not in {@code ignoredSubFilePaths}.
255*
256* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
257* is not considered empty.
258*
259* This function should ideally not be called by itself but through
260* {@link #validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String, String, List, boolean)}.
261*
262* @param subFiles The list of files of a directory to check.
263* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
264* Validation is done for the paths.
265* @return Returns {@code true} if a file was found that did not exist in the {@code ignoredSubFilePaths},
266* otherwise {@code false}.
267*/
268public static boolean nonIgnoredSubFileExists(File[] subFiles, @NonNull List<String> ignoredSubFilePaths) {
269if (subFiles == null || subFiles.length == 0) return false;
270
271String subFilePath;
272for (File subFile : subFiles) {
273subFilePath = subFile.getAbsolutePath();
274// If sub file does not exist in ignored sub file paths
275if (!ignoredSubFilePaths.contains(subFilePath)) {
276boolean isParentPath = false;
277for (String ignoredSubFilePath : ignoredSubFilePaths) {
278if (ignoredSubFilePath.startsWith(subFilePath + "/") && fileExists(ignoredSubFilePath, false)) {
279isParentPath = true;
280break;
281}
282}
283// If sub file is not a parent of any existing ignored sub file paths
284if (!isParentPath) {
285return true;
286}
287}
288
289if (getFileType(subFilePath, false) == FileType.DIRECTORY) {
290// If non ignored sub file found, then early exit, otherwise continue looking
291if (nonIgnoredSubFileExists(subFile.listFiles(), ignoredSubFilePaths))
292return true;
293}
294}
295
296return false;
297}
298
299
300
301/**
302* Checks whether a regular file exists at {@code filePath}.
303*
304* @param filePath The {@code path} for regular file to check.
305* @param followLinks The {@code boolean} that decides if symlinks will be followed while
306* finding if file exists. Check {@link #getFileType(String, boolean)}
307* for details.
308* @return Returns {@code true} if regular file exists, otherwise {@code false}.
309*/
310public static boolean regularFileExists(final String filePath, final boolean followLinks) {
311return getFileType(filePath, followLinks) == FileType.REGULAR;
312}
313
314/**
315* Checks whether a directory file exists at {@code filePath}.
316*
317* @param filePath The {@code path} for directory file to check.
318* @param followLinks The {@code boolean} that decides if symlinks will be followed while
319* finding if file exists. Check {@link #getFileType(String, boolean)}
320* for details.
321* @return Returns {@code true} if directory file exists, otherwise {@code false}.
322*/
323public static boolean directoryFileExists(final String filePath, final boolean followLinks) {
324return getFileType(filePath, followLinks) == FileType.DIRECTORY;
325}
326
327/**
328* Checks whether a symlink file exists at {@code filePath}.
329*
330* @param filePath The {@code path} for symlink file to check.
331* @return Returns {@code true} if symlink file exists, otherwise {@code false}.
332*/
333public static boolean symlinkFileExists(final String filePath) {
334return getFileType(filePath, false) == FileType.SYMLINK;
335}
336
337/**
338* Checks whether a regular or directory file exists at {@code filePath}.
339*
340* @param filePath The {@code path} for regular file to check.
341* @param followLinks The {@code boolean} that decides if symlinks will be followed while
342* finding if file exists. Check {@link #getFileType(String, boolean)}
343* for details.
344* @return Returns {@code true} if regular or directory file exists, otherwise {@code false}.
345*/
346public static boolean regularOrDirectoryFileExists(final String filePath, final boolean followLinks) {
347FileType fileType = getFileType(filePath, followLinks);
348return fileType == FileType.REGULAR || fileType == FileType.DIRECTORY;
349}
350
351/**
352* Checks whether any file exists at {@code filePath}.
353*
354* @param filePath The {@code path} for file to check.
355* @param followLinks The {@code boolean} that decides if symlinks will be followed while
356* finding if file exists. Check {@link #getFileType(String, boolean)}
357* for details.
358* @return Returns {@code true} if file exists, otherwise {@code false}.
359*/
360public static boolean fileExists(final String filePath, final boolean followLinks) {
361return getFileType(filePath, followLinks) != FileType.NO_EXIST;
362}
363
364/**
365* Get the type of file that exists at {@code filePath}.
366*
367* This function is a wrapper for
368* {@link FileTypes#getFileType(String, boolean)}
369*
370* @param filePath The {@code path} for file to check.
371* @param followLinks The {@code boolean} that decides if symlinks will be followed while
372* finding type. If set to {@code true}, then type of symlink target will
373* be returned if file at {@code filePath} is a symlink. If set to
374* {@code false}, then type of file at {@code filePath} itself will be
375* returned.
376* @return Returns the {@link FileType} of file.
377*/
378@NonNull
379public static FileType getFileType(final String filePath, final boolean followLinks) {
380return FileTypes.getFileType(filePath, followLinks);
381}
382
383
384
385/**
386* Validate the existence and permissions of regular file at path.
387*
388* If the {@code parentDirPath} is not {@code null}, then setting of missing permissions will
389* only be done if {@code path} is under {@code parentDirPath}.
390*
391* @param label The optional label for the regular file. This can optionally be {@code null}.
392* @param filePath The {@code path} for file to validate. Symlinks will not be followed.
393* @param parentDirPath The optional {@code parent directory path} to restrict operations to.
394* This can optionally be {@code null}. It is not canonicalized and only normalized.
395* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
396* @param setPermissions The {@code boolean} that decides if permissions are to be
397* automatically set defined by {@code permissionsToCheck}.
398* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
399* are to be set or if they should be overridden.
400* @param ignoreErrorsIfPathIsUnderParentDirPath The {@code boolean} that decides if permission
401* errors are to be ignored if path is under
402* {@code parentDirPath}.
403* @return Returns the {@code error} if path is not a regular file, or validating permissions
404* failed, otherwise {@code null}.
405*/
406public static Error validateRegularFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath,
407final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly,
408final boolean ignoreErrorsIfPathIsUnderParentDirPath) {
409label = (label == null || label.isEmpty() ? "" : label + " ");
410if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "regular file path", "validateRegularFileExistenceAndPermissions");
411
412try {
413FileType fileType = getFileType(filePath, false);
414
415// If file exists but not a regular file
416if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
417return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
418}
419
420boolean isPathUnderParentDirPath = false;
421if (parentDirPath != null) {
422// The path can only be under parent directory path
423isPathUnderParentDirPath = isPathInDirPath(filePath, parentDirPath, true);
424}
425
426// If setPermissions is enabled and path is a regular file
427if (setPermissions && permissionsToCheck != null && fileType == FileType.REGULAR) {
428// If there is not parentDirPath restriction or path is under parentDirPath
429if (parentDirPath == null || (isPathUnderParentDirPath && getFileType(parentDirPath, false) == FileType.DIRECTORY)) {
430if (setMissingPermissionsOnly)
431setMissingFilePermissions(label + "file", filePath, permissionsToCheck);
432else
433setFilePermissions(label + "file", filePath, permissionsToCheck);
434}
435}
436
437// If path is not a regular file
438// Regular files cannot be automatically created so we do not ignore if missing
439if (fileType != FileType.REGULAR) {
440label += "regular file";
441return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
442}
443
444// If there is not parentDirPath restriction or path is not under parentDirPath or
445// if permission errors must not be ignored for paths under parentDirPath
446if (parentDirPath == null || !isPathUnderParentDirPath || !ignoreErrorsIfPathIsUnderParentDirPath) {
447if (permissionsToCheck != null) {
448// Check if permissions are missing
449return checkMissingFilePermissions(label + "regular", filePath, permissionsToCheck, false);
450}
451}
452} catch (Exception e) {
453return FileUtilsErrno.ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
454}
455
456return null;
457
458}
459
460/**
461* Validate the existence and permissions of directory file at path.
462*
463* If the {@code parentDirPath} is not {@code null}, then creation of missing directory and
464* setting of missing permissions will only be done if {@code path} is under
465* {@code parentDirPath} or equals {@code parentDirPath}.
466*
467* @param label The optional label for the directory file. This can optionally be {@code null}.
468* @param filePath The {@code path} for file to validate or create. Symlinks will not be followed.
469* @param parentDirPath The optional {@code parent directory path} to restrict operations to.
470* This can optionally be {@code null}. It is not canonicalized and only normalized.
471* @param createDirectoryIfMissing The {@code boolean} that decides if directory file
472* should be created if its missing.
473* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
474* @param setPermissions The {@code boolean} that decides if permissions are to be
475* automatically set defined by {@code permissionsToCheck}.
476* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
477* are to be set or if they should be overridden.
478* @param ignoreErrorsIfPathIsInParentDirPath The {@code boolean} that decides if existence
479* and permission errors are to be ignored if path is
480* in {@code parentDirPath}.
481* @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission
482* error is to be ignored. This allows making an attempt to set
483* executable permissions, but ignoring if it fails.
484* @return Returns the {@code error} if path is not a directory file, failed to create it,
485* or validating permissions failed, otherwise {@code null}.
486*/
487public static Error validateDirectoryFileExistenceAndPermissions(String label, final String filePath, final String parentDirPath, final boolean createDirectoryIfMissing,
488final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly,
489final boolean ignoreErrorsIfPathIsInParentDirPath, final boolean ignoreIfNotExecutable) {
490label = (label == null || label.isEmpty() ? "" : label + " ");
491if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "directory file path", "validateDirectoryExistenceAndPermissions");
492
493try {
494File file = new File(filePath);
495FileType fileType = getFileType(filePath, false);
496
497// If file exists but not a directory file
498if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
499return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
500}
501
502boolean isPathInParentDirPath = false;
503if (parentDirPath != null) {
504// The path can be equal to parent directory path or under it
505isPathInParentDirPath = isPathInDirPath(filePath, parentDirPath, false);
506}
507
508if (createDirectoryIfMissing || setPermissions) {
509// If there is not parentDirPath restriction or path is in parentDirPath
510if (parentDirPath == null || (isPathInParentDirPath && getFileType(parentDirPath, false) == FileType.DIRECTORY)) {
511// If createDirectoryIfMissing is enabled and no file exists at path, then create directory
512if (createDirectoryIfMissing && fileType == FileType.NO_EXIST) {
513Logger.logVerbose(LOG_TAG, "Creating " + label + "directory file at path \"" + filePath + "\"");
514// Create directory and update fileType if successful, otherwise return with error
515// It "might" be possible that mkdirs returns false even though directory was created
516boolean result = file.mkdirs();
517fileType = getFileType(filePath, false);
518if (!result && fileType != FileType.DIRECTORY)
519return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "directory file", filePath);
520}
521
522// If setPermissions is enabled and path is a directory
523if (setPermissions && permissionsToCheck != null && fileType == FileType.DIRECTORY) {
524if (setMissingPermissionsOnly)
525setMissingFilePermissions(label + "directory", filePath, permissionsToCheck);
526else
527setFilePermissions(label + "directory", filePath, permissionsToCheck);
528}
529}
530}
531
532// If there is not parentDirPath restriction or path is not in parentDirPath or
533// if existence or permission errors must not be ignored for paths in parentDirPath
534if (parentDirPath == null || !isPathInParentDirPath || !ignoreErrorsIfPathIsInParentDirPath) {
535// If path is not a directory
536// Directories can be automatically created so we can ignore if missing with above check
537if (fileType != FileType.DIRECTORY) {
538label += "directory";
539return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
540}
541
542if (permissionsToCheck != null) {
543// Check if permissions are missing
544return checkMissingFilePermissions(label + "directory", filePath, permissionsToCheck, ignoreIfNotExecutable);
545}
546}
547} catch (Exception e) {
548return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION.getError(e, label + "directory file", filePath, e.getMessage());
549}
550
551return null;
552}
553
554
555
556/**
557* Create a regular file at path.
558*
559* This function is a wrapper for
560* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
561*
562* @param filePath The {@code path} for regular file to create.
563* @return Returns the {@code error} if path is not a regular file or failed to create it,
564* otherwise {@code null}.
565*/
566public static Error createRegularFile(final String filePath) {
567return createRegularFile(null, filePath);
568}
569
570/**
571* Create a regular file at path.
572*
573* This function is a wrapper for
574* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
575*
576* @param label The optional label for the regular file. This can optionally be {@code null}.
577* @param filePath The {@code path} for regular file to create.
578* @return Returns the {@code error} if path is not a regular file or failed to create it,
579* otherwise {@code null}.
580*/
581public static Error createRegularFile(final String label, final String filePath) {
582return createRegularFile(label, filePath,
583null, false, false);
584}
585
586/**
587* Create a regular file at path.
588*
589* This function is a wrapper for
590* {@link #validateRegularFileExistenceAndPermissions(String, String, String, String, boolean, boolean, boolean)}.
591*
592* @param label The optional label for the regular file. This can optionally be {@code null}.
593* @param filePath The {@code path} for regular file to create.
594* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
595* @param setPermissions The {@code boolean} that decides if permissions are to be
596* automatically set defined by {@code permissionsToCheck}.
597* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
598* are to be set or if they should be overridden.
599* @return Returns the {@code error} if path is not a regular file, failed to create it,
600* or validating permissions failed, otherwise {@code null}.
601*/
602public static Error createRegularFile(String label, final String filePath,
603final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) {
604label = (label == null || label.isEmpty() ? "" : label + " ");
605if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createRegularFile");
606
607Error error;
608
609File file = new File(filePath);
610FileType fileType = getFileType(filePath, false);
611
612// If file exists but not a regular file
613if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
614return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
615}
616
617// If regular file already exists
618if (fileType == FileType.REGULAR) {
619return null;
620}
621
622// Create the file parent directory
623error = createParentDirectoryFile(label + "regular file parent", filePath);
624if (error != null)
625return error;
626
627try {
628Logger.logVerbose(LOG_TAG, "Creating " + label + "regular file at path \"" + filePath + "\"");
629
630if (!file.createNewFile())
631return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED.getError(label + "regular file", filePath);
632} catch (Exception e) {
633return FileUtilsErrno.ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "regular file", filePath, e.getMessage());
634}
635
636return validateRegularFileExistenceAndPermissions(label, filePath,
637null,
638permissionsToCheck, setPermissions, setMissingPermissionsOnly,
639false);
640}
641
642
643
644/**
645* Create parent directory of file at path.
646*
647* This function is a wrapper for
648* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
649*
650* @param label The optional label for the parent directory file. This can optionally be {@code null}.
651* @param filePath The {@code path} for file whose parent needs to be created.
652* @return Returns the {@code error} if parent path is not a directory file or failed to create it,
653* otherwise {@code null}.
654*/
655public static Error createParentDirectoryFile(final String label, final String filePath) {
656if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "createParentDirectoryFile");
657
658File file = new File(filePath);
659String fileParentPath = file.getParent();
660
661if (fileParentPath != null)
662return createDirectoryFile(label, fileParentPath,
663null, false, false);
664else
665return null;
666}
667
668/**
669* Create a directory file at path.
670*
671* This function is a wrapper for
672* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
673*
674* @param filePath The {@code path} for directory file to create.
675* @return Returns the {@code error} if path is not a directory file or failed to create it,
676* otherwise {@code null}.
677*/
678public static Error createDirectoryFile(final String filePath) {
679return createDirectoryFile(null, filePath);
680}
681
682/**
683* Create a directory file at path.
684*
685* This function is a wrapper for
686* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
687*
688* @param label The optional label for the directory file. This can optionally be {@code null}.
689* @param filePath The {@code path} for directory file to create.
690* @return Returns the {@code error} if path is not a directory file or failed to create it,
691* otherwise {@code null}.
692*/
693public static Error createDirectoryFile(final String label, final String filePath) {
694return createDirectoryFile(label, filePath,
695null, false, false);
696}
697
698/**
699* Create a directory file at path.
700*
701* This function is a wrapper for
702* {@link #validateDirectoryFileExistenceAndPermissions(String, String, String, boolean, String, boolean, boolean, boolean, boolean)}.
703*
704* @param label The optional label for the directory file. This can optionally be {@code null}.
705* @param filePath The {@code path} for directory file to create.
706* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
707* @param setPermissions The {@code boolean} that decides if permissions are to be
708* automatically set defined by {@code permissionsToCheck}.
709* @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions
710* are to be set or if they should be overridden.
711* @return Returns the {@code error} if path is not a directory file, failed to create it,
712* or validating permissions failed, otherwise {@code null}.
713*/
714public static Error createDirectoryFile(final String label, final String filePath,
715final String permissionsToCheck, final boolean setPermissions, final boolean setMissingPermissionsOnly) {
716return validateDirectoryFileExistenceAndPermissions(label, filePath,
717null, true,
718permissionsToCheck, setPermissions, setMissingPermissionsOnly,
719false, false);
720}
721
722
723
724/**
725* Create a symlink file at path.
726*
727* This function is a wrapper for
728* {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}.
729*
730* Dangling symlinks will be allowed.
731* Symlink destination will be overwritten if it already exists but only if its a symlink.
732*
733* @param targetFilePath The {@code path} TO which the symlink file will be created.
734* @param destFilePath The {@code path} AT which the symlink file will be created.
735* @return Returns the {@code error} if path is not a symlink file, failed to create it,
736* otherwise {@code null}.
737*/
738public static Error createSymlinkFile(final String targetFilePath, final String destFilePath) {
739return createSymlinkFile(null, targetFilePath, destFilePath,
740true, true, true);
741}
742
743/**
744* Create a symlink file at path.
745*
746* This function is a wrapper for
747* {@link #createSymlinkFile(String, String, String, boolean, boolean, boolean)}.
748*
749* Dangling symlinks will be allowed.
750* Symlink destination will be overwritten if it already exists but only if its a symlink.
751*
752* @param label The optional label for the symlink file. This can optionally be {@code null}.
753* @param targetFilePath The {@code path} TO which the symlink file will be created.
754* @param destFilePath The {@code path} AT which the symlink file will be created.
755* @return Returns the {@code error} if path is not a symlink file, failed to create it,
756* otherwise {@code null}.
757*/
758public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath) {
759return createSymlinkFile(label, targetFilePath, destFilePath,
760true, true, true);
761}
762
763/**
764* Create a symlink file at path.
765*
766* @param label The optional label for the symlink file. This can optionally be {@code null}.
767* @param targetFilePath The {@code path} TO which the symlink file will be created.
768* @param destFilePath The {@code path} AT which the symlink file will be created.
769* @param allowDangling The {@code boolean} that decides if it should be considered an
770* error if source file doesn't exist.
771* @param overwrite The {@code boolean} that decides if destination file should be overwritten if
772* it already exists. If set to {@code true}, then destination file will be
773* deleted before symlink is created.
774* @param overwriteOnlyIfDestIsASymlink The {@code boolean} that decides if overwrite should
775* only be done if destination file is also a symlink.
776* @return Returns the {@code error} if path is not a symlink file, failed to create it,
777* or validating permissions failed, otherwise {@code null}.
778*/
779public static Error createSymlinkFile(String label, final String targetFilePath, final String destFilePath,
780final boolean allowDangling, final boolean overwrite, final boolean overwriteOnlyIfDestIsASymlink) {
781label = (label == null || label.isEmpty() ? "" : label + " ");
782if (targetFilePath == null || targetFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "target file path", "createSymlinkFile");
783if (destFilePath == null || destFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "destination file path", "createSymlinkFile");
784
785Error error;
786
787try {
788File destFile = new File(destFilePath);
789
790String targetFileAbsolutePath = targetFilePath;
791// If target path is relative instead of absolute
792if (!targetFilePath.startsWith("/")) {
793String destFileParentPath = destFile.getParent();
794if (destFileParentPath != null)
795targetFileAbsolutePath = destFileParentPath + "/" + targetFilePath;
796}
797
798FileType targetFileType = getFileType(targetFileAbsolutePath, false);
799FileType destFileType = getFileType(destFilePath, false);
800
801// If target file does not exist
802if (targetFileType == FileType.NO_EXIST) {
803// If dangling symlink should not be allowed, then return with error
804if (!allowDangling) {
805label += "symlink target file";
806return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, targetFileAbsolutePath).setLabel(label);
807}
808}
809
810// If destination exists
811if (destFileType != FileType.NO_EXIST) {
812// If destination must not be overwritten
813if (!overwrite) {
814return null;
815}
816
817// If overwriteOnlyIfDestIsASymlink is enabled but destination file is not a symlink
818if (overwriteOnlyIfDestIsASymlink && destFileType != FileType.SYMLINK)
819return FileUtilsErrno.ERRNO_CANNOT_OVERWRITE_A_NON_SYMLINK_FILE_TYPE.getError(label + " file", destFilePath, targetFilePath, destFileType.getName());
820
821// Delete the destination file
822error = deleteFile(label + "symlink destination", destFilePath, true);
823if (error != null)
824return error;
825} else {
826// Create the destination file parent directory
827error = createParentDirectoryFile(label + "symlink destination file parent", destFilePath);
828if (error != null)
829return error;
830}
831
832// create a symlink at destFilePath to targetFilePath
833Logger.logVerbose(LOG_TAG, "Creating " + label + "symlink file at path \"" + destFilePath + "\" to \"" + targetFilePath + "\"");
834Os.symlink(targetFilePath, destFilePath);
835} catch (Exception e) {
836return FileUtilsErrno.ERRNO_CREATING_SYMLINK_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "symlink file", destFilePath, targetFilePath, e.getMessage());
837}
838
839return null;
840}
841
842
843
844/**
845* Copy a regular file from {@code sourceFilePath} to {@code destFilePath}.
846*
847* This function is a wrapper for
848* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
849*
850* If destination file already exists, then it will be overwritten, but only if its a regular
851* file, otherwise an error will be returned.
852*
853* @param label The optional label for file to copy. This can optionally be {@code null}.
854* @param srcFilePath The {@code source path} for file to copy.
855* @param destFilePath The {@code destination path} for file to copy.
856* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
857* error if source file to copied doesn't exist.
858* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
859*/
860public static Error copyRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
861return copyOrMoveFile(label, srcFilePath, destFilePath,
862false, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(),
863true, true);
864}
865
866/**
867* Move a regular file from {@code sourceFilePath} to {@code destFilePath}.
868*
869* This function is a wrapper for
870* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
871*
872* If destination file already exists, then it will be overwritten, but only if its a regular
873* file, otherwise an error will be returned.
874*
875* @param label The optional label for file to move. This can optionally be {@code null}.
876* @param srcFilePath The {@code source path} for file to move.
877* @param destFilePath The {@code destination path} for file to move.
878* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
879* error if source file to moved doesn't exist.
880* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
881*/
882public static Error moveRegularFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
883return copyOrMoveFile(label, srcFilePath, destFilePath,
884true, ignoreNonExistentSrcFile, FileType.REGULAR.getValue(),
885true, true);
886}
887
888/**
889* Copy a directory file from {@code sourceFilePath} to {@code destFilePath}.
890*
891* This function is a wrapper for
892* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
893*
894* If destination file already exists, then it will be overwritten, but only if its a directory
895* file, otherwise an error will be returned.
896*
897* @param label The optional label for file to copy. This can optionally be {@code null}.
898* @param srcFilePath The {@code source path} for file to copy.
899* @param destFilePath The {@code destination path} for file to copy.
900* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
901* error if source file to copied doesn't exist.
902* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
903*/
904public static Error copyDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
905return copyOrMoveFile(label, srcFilePath, destFilePath,
906false, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(),
907true, true);
908}
909
910/**
911* Move a directory file from {@code sourceFilePath} to {@code destFilePath}.
912*
913* This function is a wrapper for
914* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
915*
916* If destination file already exists, then it will be overwritten, but only if its a directory
917* file, otherwise an error will be returned.
918*
919* @param label The optional label for file to move. This can optionally be {@code null}.
920* @param srcFilePath The {@code source path} for file to move.
921* @param destFilePath The {@code destination path} for file to move.
922* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
923* error if source file to moved doesn't exist.
924* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
925*/
926public static Error moveDirectoryFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
927return copyOrMoveFile(label, srcFilePath, destFilePath,
928true, ignoreNonExistentSrcFile, FileType.DIRECTORY.getValue(),
929true, true);
930}
931
932/**
933* Copy a symlink file from {@code sourceFilePath} to {@code destFilePath}.
934*
935* This function is a wrapper for
936* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
937*
938* If destination file already exists, then it will be overwritten, but only if its a symlink
939* file, otherwise an error will be returned.
940*
941* @param label The optional label for file to copy. This can optionally be {@code null}.
942* @param srcFilePath The {@code source path} for file to copy.
943* @param destFilePath The {@code destination path} for file to copy.
944* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
945* error if source file to copied doesn't exist.
946* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
947*/
948public static Error copySymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
949return copyOrMoveFile(label, srcFilePath, destFilePath,
950false, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(),
951true, true);
952}
953
954/**
955* Move a symlink file from {@code sourceFilePath} to {@code destFilePath}.
956*
957* This function is a wrapper for
958* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
959*
960* If destination file already exists, then it will be overwritten, but only if its a symlink
961* file, otherwise an error will be returned.
962*
963* @param label The optional label for file to move. This can optionally be {@code null}.
964* @param srcFilePath The {@code source path} for file to move.
965* @param destFilePath The {@code destination path} for file to move.
966* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
967* error if source file to moved doesn't exist.
968* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
969*/
970public static Error moveSymlinkFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
971return copyOrMoveFile(label, srcFilePath, destFilePath,
972true, ignoreNonExistentSrcFile, FileType.SYMLINK.getValue(),
973true, true);
974}
975
976/**
977* Copy a file from {@code sourceFilePath} to {@code destFilePath}.
978*
979* This function is a wrapper for
980* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
981*
982* If destination file already exists, then it will be overwritten, but only if its the same file
983* type as the source, otherwise an error will be returned.
984*
985* @param label The optional label for file to copy. This can optionally be {@code null}.
986* @param srcFilePath The {@code source path} for file to copy.
987* @param destFilePath The {@code destination path} for file to copy.
988* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
989* error if source file to copied doesn't exist.
990* @return Returns the {@code error} if copy was not successful, otherwise {@code null}.
991*/
992public static Error copyFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
993return copyOrMoveFile(label, srcFilePath, destFilePath,
994false, ignoreNonExistentSrcFile, FileTypes.FILE_TYPE_NORMAL_FLAGS,
995true, true);
996}
997
998/**
999* Move a file from {@code sourceFilePath} to {@code destFilePath}.
1000*
1001* This function is a wrapper for
1002* {@link #copyOrMoveFile(String, String, String, boolean, boolean, int, boolean, boolean)}.
1003*
1004* If destination file already exists, then it will be overwritten, but only if its the same file
1005* type as the source, otherwise an error will be returned.
1006*
1007* @param label The optional label for file to move. This can optionally be {@code null}.
1008* @param srcFilePath The {@code source path} for file to move.
1009* @param destFilePath The {@code destination path} for file to move.
1010* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
1011* error if source file to moved doesn't exist.
1012* @return Returns the {@code error} if move was not successful, otherwise {@code null}.
1013*/
1014public static Error moveFile(final String label, final String srcFilePath, final String destFilePath, final boolean ignoreNonExistentSrcFile) {
1015return copyOrMoveFile(label, srcFilePath, destFilePath,
1016true, ignoreNonExistentSrcFile, FileTypes.FILE_TYPE_NORMAL_FLAGS,
1017true, true);
1018}
1019
1020/**
1021* Copy or move a file from {@code sourceFilePath} to {@code destFilePath}.
1022*
1023* The {@code sourceFilePath} and {@code destFilePath} must be the canonical path to the source
1024* and destination since symlinks will not be followed.
1025*
1026* If the {@code sourceFilePath} or {@code destFilePath} is a canonical path to a directory,
1027* then any symlink files found under the directory will be deleted, but not their targets when
1028* deleting source after move and deleting destination before copy/move.
1029*
1030* @param label The optional label for file to copy or move. This can optionally be {@code null}.
1031* @param srcFilePath The {@code source path} for file to copy or move.
1032* @param destFilePath The {@code destination path} for file to copy or move.
1033* @param moveFile The {@code boolean} that decides if source file needs to be copied or moved.
1034* If set to {@code true}, then source file will be moved, otherwise it will be
1035* copied.
1036* @param ignoreNonExistentSrcFile The {@code boolean} that decides if it should be considered an
1037* error if source file to copied or moved doesn't exist.
1038* @param allowedFileTypeFlags The flags that are matched against the source file's {@link FileType}
1039* to see if it should be copied/moved or not. This is a safety measure
1040* to prevent accidental copy/move/delete of the wrong type of file,
1041* like a directory instead of a regular file. You can pass
1042* {@link FileTypes#FILE_TYPE_ANY_FLAGS} to allow copy/move of any file type.
1043* @param overwrite The {@code boolean} that decides if destination file should be overwritten if
1044* it already exists. If set to {@code true}, then destination file will be
1045* deleted before source is copied or moved.
1046* @param overwriteOnlyIfDestSameFileTypeAsSrc The {@code boolean} that decides if overwrite should
1047* only be done if destination file is also the same file
1048* type as the source file.
1049* @return Returns the {@code error} if copy or move was not successful, otherwise {@code null}.
1050*/
1051public static Error copyOrMoveFile(String label, final String srcFilePath, final String destFilePath,
1052final boolean moveFile, final boolean ignoreNonExistentSrcFile, int allowedFileTypeFlags,
1053final boolean overwrite, final boolean overwriteOnlyIfDestSameFileTypeAsSrc) {
1054label = (label == null || label.isEmpty() ? "" : label + " ");
1055if (srcFilePath == null || srcFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "source file path", "copyOrMoveFile");
1056if (destFilePath == null || destFilePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "destination file path", "copyOrMoveFile");
1057
1058String mode = (moveFile ? "Moving" : "Copying");
1059String modePast = (moveFile ? "moved" : "copied");
1060
1061Error error;
1062
1063try {
1064Logger.logVerbose(LOG_TAG, mode + " " + label + "source file from \"" + srcFilePath + "\" to destination \"" + destFilePath + "\"");
1065
1066File srcFile = new File(srcFilePath);
1067File destFile = new File(destFilePath);
1068
1069FileType srcFileType = getFileType(srcFilePath, false);
1070FileType destFileType = getFileType(destFilePath, false);
1071
1072String srcFileCanonicalPath = srcFile.getCanonicalPath();
1073String destFileCanonicalPath = destFile.getCanonicalPath();
1074
1075// If source file does not exist
1076if (srcFileType == FileType.NO_EXIST) {
1077// If copy or move is to be ignored if source file is not found
1078if (ignoreNonExistentSrcFile)
1079return null;
1080// Else return with error
1081else {
1082label += "source file";
1083return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, srcFilePath).setLabel(label);
1084}
1085}
1086
1087// If the file type of the source file does not exist in the allowedFileTypeFlags, then return with error
1088if ((allowedFileTypeFlags & srcFileType.getValue()) <= 0)
1089return FileUtilsErrno.ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE.getError(label + "source file meant to be " + modePast, srcFilePath, FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags));
1090
1091// If source and destination file path are the same
1092if (srcFileCanonicalPath.equals(destFileCanonicalPath))
1093return FileUtilsErrno.ERRNO_COPYING_OR_MOVING_FILE_TO_SAME_PATH.getError(mode + " " + label + "source file", srcFilePath, destFilePath);
1094
1095// If destination exists
1096if (destFileType != FileType.NO_EXIST) {
1097// If destination must not be overwritten
1098if (!overwrite) {
1099return null;
1100}
1101
1102// If overwriteOnlyIfDestSameFileTypeAsSrc is enabled but destination file does not match source file type
1103if (overwriteOnlyIfDestSameFileTypeAsSrc && destFileType != srcFileType)
1104return FileUtilsErrno.ERRNO_CANNOT_OVERWRITE_A_DIFFERENT_FILE_TYPE.getError(label + "source file", mode.toLowerCase(), srcFilePath, destFilePath, destFileType.getName(), srcFileType.getName());
1105
1106// Delete the destination file
1107error = deleteFile(label + "destination", destFilePath, true);
1108if (error != null)
1109return error;
1110}
1111
1112
1113// Copy or move source file to dest
1114boolean copyFile = !moveFile;
1115
1116// If moveFile is true
1117if (moveFile) {
1118// We first try to rename source file to destination file to save a copy operation in case both source and destination are on the same filesystem
1119Logger.logVerbose(LOG_TAG, "Attempting to rename source to destination.");
1120
1121// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/io/UnixFileSystem.java;l=358
1122// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/luni/src/main/java/android/system/Os.java;l=512
1123// Uses File.getPath() to get the path of source and destination and not the canonical path
1124if (!srcFile.renameTo(destFile)) {
1125// If destination directory is a subdirectory of the source directory
1126// Copying is still allowed by copyDirectory() by excluding destination directory files
1127if (srcFileType == FileType.DIRECTORY && destFileCanonicalPath.startsWith(srcFileCanonicalPath + File.separator))
1128return FileUtilsErrno.ERRNO_CANNOT_MOVE_DIRECTORY_TO_SUB_DIRECTORY_OF_ITSELF.getError(label + "source directory", srcFilePath, destFilePath);
1129
1130// If rename failed, then we copy
1131Logger.logVerbose(LOG_TAG, "Renaming " + label + "source file to destination failed, attempting to copy.");
1132copyFile = true;
1133}
1134}
1135
1136// If moveFile is false or renameTo failed while moving
1137if (copyFile) {
1138Logger.logVerbose(LOG_TAG, "Attempting to copy source to destination.");
1139
1140// Create the dest file parent directory
1141error = createParentDirectoryFile(label + "dest file parent", destFilePath);
1142if (error != null)
1143return error;
1144
1145if (srcFileType == FileType.DIRECTORY) {
1146// Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5
1147org.apache.commons.io.FileUtils.copyDirectory(srcFile, destFile, true);
1148} else if (srcFileType == FileType.SYMLINK) {
1149if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1150java.nio.file.Files.copy(srcFile.toPath(), destFile.toPath(), LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
1151} else {
1152// read the target for the source file and create a symlink at dest
1153// source file metadata will be lost
1154error = createSymlinkFile(label + "dest", Os.readlink(srcFilePath), destFilePath);
1155if (error != null)
1156return error;
1157}
1158} else {
1159if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1160java.nio.file.Files.copy(srcFile.toPath(), destFile.toPath(), LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
1161} else {
1162// Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5
1163org.apache.commons.io.FileUtils.copyFile(srcFile, destFile, true);
1164}
1165}
1166}
1167
1168// If source file had to be moved
1169if (moveFile) {
1170// Delete the source file since copying would have succeeded
1171error = deleteFile(label + "source", srcFilePath, true);
1172if (error != null)
1173return error;
1174}
1175
1176Logger.logVerbose(LOG_TAG, mode + " successful.");
1177}
1178catch (Exception e) {
1179return FileUtilsErrno.ERRNO_COPYING_OR_MOVING_FILE_FAILED_WITH_EXCEPTION.getError(e, mode + " " + label + "file", srcFilePath, destFilePath, e.getMessage());
1180}
1181
1182return null;
1183}
1184
1185
1186
1187/**
1188* Delete regular file at path.
1189*
1190* This function is a wrapper for {@link #deleteFile(String, String, boolean, boolean, int)}.
1191*
1192* @param label The optional label for file to delete. This can optionally be {@code null}.
1193* @param filePath The {@code path} for file to delete.
1194* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1195* error if file to deleted doesn't exist.
1196* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1197*/
1198public static Error deleteRegularFile(String label, final String filePath, final boolean ignoreNonExistentFile) {
1199return deleteFile(label, filePath, ignoreNonExistentFile, false, FileType.REGULAR.getValue());
1200}
1201
1202/**
1203* Delete directory file at path.
1204*
1205* This function is a wrapper for {@link #deleteFile(String, String, boolean, boolean, int)}.
1206*
1207* @param label The optional label for file to delete. This can optionally be {@code null}.
1208* @param filePath The {@code path} for file to delete.
1209* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1210* error if file to deleted doesn't exist.
1211* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1212*/
1213public static Error deleteDirectoryFile(String label, final String filePath, final boolean ignoreNonExistentFile) {
1214return deleteFile(label, filePath, ignoreNonExistentFile, false, FileType.DIRECTORY.getValue());
1215}
1216
1217/**
1218* Delete symlink file at path.
1219*
1220* This function is a wrapper for {@link #deleteFile(String, String, boolean, boolean, int)}.
1221*
1222* @param label The optional label for file to delete. This can optionally be {@code null}.
1223* @param filePath The {@code path} for file to delete.
1224* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1225* error if file to deleted doesn't exist.
1226* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1227*/
1228public static Error deleteSymlinkFile(String label, final String filePath, final boolean ignoreNonExistentFile) {
1229return deleteFile(label, filePath, ignoreNonExistentFile, false, FileType.SYMLINK.getValue());
1230}
1231
1232/**
1233* Delete socket file at path.
1234*
1235* This function is a wrapper for {@link #deleteFile(String, String, boolean, boolean, int)}.
1236*
1237* @param label The optional label for file to delete. This can optionally be {@code null}.
1238* @param filePath The {@code path} for file to delete.
1239* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1240* error if file to deleted doesn't exist.
1241* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1242*/
1243public static Error deleteSocketFile(String label, final String filePath, final boolean ignoreNonExistentFile) {
1244return deleteFile(label, filePath, ignoreNonExistentFile, false, FileType.SOCKET.getValue());
1245}
1246
1247/**
1248* Delete regular, directory or symlink file at path.
1249*
1250* This function is a wrapper for {@link #deleteFile(String, String, boolean, boolean, int)}.
1251*
1252* @param label The optional label for file to delete. This can optionally be {@code null}.
1253* @param filePath The {@code path} for file to delete.
1254* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1255* error if file to deleted doesn't exist.
1256* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1257*/
1258public static Error deleteFile(String label, final String filePath, final boolean ignoreNonExistentFile) {
1259return deleteFile(label, filePath, ignoreNonExistentFile, false, FileTypes.FILE_TYPE_NORMAL_FLAGS);
1260}
1261
1262/**
1263* Delete file at path.
1264*
1265* The {@code filePath} must be the canonical path to the file to be deleted since symlinks will
1266* not be followed.
1267* If the {@code filePath} is a canonical path to a directory, then any symlink files found under
1268* the directory will be deleted, but not their targets.
1269*
1270* @param label The optional label for file to delete. This can optionally be {@code null}.
1271* @param filePath The {@code path} for file to delete.
1272* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1273* error if file to deleted doesn't exist.
1274* @param ignoreWrongFileType The {@code boolean} that decides if it should be considered an
1275* error if file type is not one from {@code allowedFileTypeFlags}.
1276* @param allowedFileTypeFlags The flags that are matched against the file's {@link FileType} to
1277* see if it should be deleted or not. This is a safety measure to
1278* prevent accidental deletion of the wrong type of file, like a
1279* directory instead of a regular file. You can pass
1280* {@link FileTypes#FILE_TYPE_ANY_FLAGS} to allow deletion of any file type.
1281* @return Returns the {@code error} if deletion was not successful, otherwise {@code null}.
1282*/
1283public static Error deleteFile(String label, final String filePath, final boolean ignoreNonExistentFile, final boolean ignoreWrongFileType, int allowedFileTypeFlags) {
1284label = (label == null || label.isEmpty() ? "" : label + " ");
1285if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "deleteFile");
1286
1287try {
1288File file = new File(filePath);
1289FileType fileType = getFileType(filePath, false);
1290
1291Logger.logVerbose(LOG_TAG, "Processing delete of " + label + "file at path \"" + filePath + "\" of type \"" + fileType.getName() + "\"");
1292
1293// If file does not exist
1294if (fileType == FileType.NO_EXIST) {
1295// If delete is to be ignored if file does not exist
1296if (ignoreNonExistentFile)
1297return null;
1298// Else return with error
1299else {
1300label += "file meant to be deleted";
1301return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
1302}
1303}
1304
1305// If the file type of the file does not exist in the allowedFileTypeFlags
1306if ((allowedFileTypeFlags & fileType.getValue()) <= 0) {
1307// If wrong file type is to be ignored
1308if (ignoreWrongFileType) {
1309Logger.logVerbose(LOG_TAG, "Ignoring deletion of " + label + "file at path \"" + filePath + "\" of type \"" + fileType.getName() + "\" not matching allowed file types: " + FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags));
1310return null;
1311}
1312
1313// Else return with error
1314return FileUtilsErrno.ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE.getError(label + "file meant to be deleted", filePath, fileType.getName(), FileTypes.convertFileTypeFlagsToNamesString(allowedFileTypeFlags));
1315}
1316
1317Logger.logVerbose(LOG_TAG, "Deleting " + label + "file at path \"" + filePath + "\"");
1318
1319if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1320/*
1321* Try to use {@link SecureDirectoryStream} if available for safer directory
1322* deletion, it should be available for android >= 8.0
1323* https://guava.dev/releases/24.1-jre/api/docs/com/google/common/io/MoreFiles.html#deleteRecursively-java.nio.file.Path-com.google.common.io.RecursiveDeleteOption...-
1324* https://github.com/google/guava/issues/365
1325* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/sun/nio/fs/UnixSecureDirectoryStream.java
1326*
1327* MoreUtils is marked with the @Beta annotation so the API may be removed in
1328* future but has been there for a few years now.
1329*
1330* If an exception is thrown, the exception message might not contain the full errors.
1331* Individual failures get added to suppressed throwables which can be extracted
1332* from the exception object by calling `Throwable[] getSuppressed()`. So just logging
1333* the exception message and stacktrace may not be enough, the suppressed throwables
1334* need to be logged as well, which the Logger class does if they are found in the
1335* exception added to the Error that's returned by this function.
1336* https://github.com/google/guava/blob/v30.1.1/guava/src/com/google/common/io/MoreFiles.java#L775
1337*/
1338//noinspection UnstableApiUsage
1339com.google.common.io.MoreFiles.deleteRecursively(file.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
1340} else {
1341if (fileType == FileType.DIRECTORY) {
1342// deleteDirectory() instead of forceDelete() gets the files list first instead of walking directory tree, so seems safer
1343// Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5
1344org.apache.commons.io.FileUtils.deleteDirectory(file);
1345} else {
1346// Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5
1347org.apache.commons.io.FileUtils.forceDelete(file);
1348}
1349}
1350
1351// If file still exists after deleting it
1352fileType = getFileType(filePath, false);
1353if (fileType != FileType.NO_EXIST)
1354return FileUtilsErrno.ERRNO_FILE_STILL_EXISTS_AFTER_DELETING.getError(label + "file meant to be deleted", filePath);
1355}
1356catch (Exception e) {
1357return FileUtilsErrno.ERRNO_DELETING_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
1358}
1359
1360return null;
1361}
1362
1363
1364
1365/**
1366* Clear contents of directory at path without deleting the directory. If directory does not exist
1367* it will be created automatically.
1368*
1369* This function is a wrapper for
1370* {@link #clearDirectory(String, String)}.
1371*
1372* @param filePath The {@code path} for directory to clear.
1373* @return Returns the {@code error} if clearing was not successful, otherwise {@code null}.
1374*/
1375public static Error clearDirectory(String filePath) {
1376return clearDirectory(null, filePath);
1377}
1378
1379/**
1380* Clear contents of directory at path without deleting the directory. If directory does not exist
1381* it will be created automatically.
1382*
1383* The {@code filePath} must be the canonical path to a directory since symlinks will not be followed.
1384* Any symlink files found under the directory will be deleted, but not their targets.
1385*
1386* @param label The optional label for directory to clear. This can optionally be {@code null}.
1387* @param filePath The {@code path} for directory to clear.
1388* @return Returns the {@code error} if clearing was not successful, otherwise {@code null}.
1389*/
1390public static Error clearDirectory(String label, final String filePath) {
1391label = (label == null || label.isEmpty() ? "" : label + " ");
1392if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "clearDirectory");
1393
1394Error error;
1395
1396try {
1397Logger.logVerbose(LOG_TAG, "Clearing " + label + "directory at path \"" + filePath + "\"");
1398
1399File file = new File(filePath);
1400FileType fileType = getFileType(filePath, false);
1401
1402// If file exists but not a directory file
1403if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
1404return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
1405}
1406
1407// If directory exists, clear its contents
1408if (fileType == FileType.DIRECTORY) {
1409if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1410/* If an exception is thrown, the exception message might not contain the full errors.
1411* Individual failures get added to suppressed throwables. */
1412//noinspection UnstableApiUsage
1413com.google.common.io.MoreFiles.deleteDirectoryContents(file.toPath(), RecursiveDeleteOption.ALLOW_INSECURE);
1414} else {
1415// Will give runtime exceptions on android < 8 due to missing classes like java.nio.file.Path if org.apache.commons.io version > 2.5
1416org.apache.commons.io.FileUtils.cleanDirectory(new File(filePath));
1417}
1418}
1419// Else create it
1420else {
1421error = createDirectoryFile(label, filePath);
1422if (error != null)
1423return error;
1424}
1425} catch (Exception e) {
1426return FileUtilsErrno.ERRNO_CLEARING_DIRECTORY_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, e.getMessage());
1427}
1428
1429return null;
1430}
1431
1432/**
1433* Delete files under a directory older than x days.
1434*
1435* The {@code filePath} must be the canonical path to a directory since symlinks will not be followed.
1436* Any symlink files found under the directory will be deleted, but not their targets.
1437*
1438* @param label The optional label for directory to clear. This can optionally be {@code null}.
1439* @param filePath The {@code path} for directory to clear.
1440* @param dirFilter The optional filter to apply when finding subdirectories.
1441* If this parameter is {@code null}, subdirectories will not be included in the
1442* search. Use TrueFileFilter.INSTANCE to match all directories.
1443* @param days The x amount of days before which files should be deleted. This must be `>=0`.
1444* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1445* error if file to deleted doesn't exist.
1446* @param allowedFileTypeFlags The flags that are matched against the file's {@link FileType} to
1447* see if it should be deleted or not. This is a safety measure to
1448* prevent accidental deletion of the wrong type of file, like a
1449* directory instead of a regular file. You can pass
1450* {@link FileTypes#FILE_TYPE_ANY_FLAGS} to allow deletion of any file type.
1451* @return Returns the {@code error} if deleting was not successful, otherwise {@code null}.
1452*/
1453public static Error deleteFilesOlderThanXDays(String label, final String filePath, final IOFileFilter dirFilter, int days, final boolean ignoreNonExistentFile, int allowedFileTypeFlags) {
1454label = (label == null || label.isEmpty() ? "" : label + " ");
1455if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "deleteFilesOlderThanXDays");
1456if (days < 0) return FunctionErrno.ERRNO_INVALID_PARAMETER.getError(label + "days", "deleteFilesOlderThanXDays", " It must be >= 0.");
1457
1458Error error;
1459
1460try {
1461Logger.logVerbose(LOG_TAG, "Deleting files under " + label + "directory at path \"" + filePath + "\" older than " + days + " days");
1462
1463File file = new File(filePath);
1464FileType fileType = getFileType(filePath, false);
1465
1466// If file exists but not a directory file
1467if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
1468return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
1469}
1470
1471// If file does not exist
1472if (fileType == FileType.NO_EXIST) {
1473// If delete is to be ignored if file does not exist
1474if (ignoreNonExistentFile)
1475return null;
1476// Else return with error
1477else {
1478label += "directory under which files had to be deleted";
1479return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
1480}
1481}
1482
1483// TODO: Use FileAttributes with support for atime (default), mtime, ctime. Add regex for ignoring file and dir absolute paths.
1484// FIXME: iterateFiles() does not return subdirectories even with TrueFileFilter for file and dir.
1485// FIXME: Empty directories remain
1486
1487// If directory exists, delete its contents
1488Calendar calendar = Calendar.getInstance();
1489calendar.add(Calendar.DATE, -(days));
1490// AgeFileFilter seems to apply to symlink destination timestamp instead of symlink file itself
1491Iterator<File> filesToDelete =
1492org.apache.commons.io.FileUtils.iterateFiles(file, new AgeFileFilter(calendar.getTime()), dirFilter);
1493while (filesToDelete.hasNext()) {
1494File subFile = filesToDelete.next();
1495error = deleteFile(label + " directory sub", subFile.getAbsolutePath(), true, true, allowedFileTypeFlags);
1496if (error != null)
1497return error;
1498}
1499} catch (Exception e) {
1500return FileUtilsErrno.ERRNO_DELETING_FILES_OLDER_THAN_X_DAYS_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, days, e.getMessage());
1501}
1502
1503return null;
1504
1505}
1506
1507
1508
1509
1510
1511/**
1512* Read a text {@link String} from file at path with a specific {@link Charset} into {@code dataString}.
1513*
1514* @param label The optional label for file to read. This can optionally be {@code null}.
1515* @param filePath The {@code path} for file to read.
1516* @param charset The {@link Charset} of the file. If this is {@code null},
1517* then default {@link Charset} will be used.
1518* @param dataStringBuilder The {@code StringBuilder} to read data into.
1519* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1520* error if file to read doesn't exist.
1521* @return Returns the {@code error} if reading was not successful, otherwise {@code null}.
1522*/
1523public static Error readTextFromFile(String label, final String filePath, Charset charset, @NonNull final StringBuilder dataStringBuilder, final boolean ignoreNonExistentFile) {
1524label = (label == null || label.isEmpty() ? "" : label + " ");
1525if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "readStringFromFile");
1526
1527Logger.logVerbose(LOG_TAG, "Reading text from " + label + "file at path \"" + filePath + "\"");
1528
1529Error error;
1530
1531FileType fileType = getFileType(filePath, false);
1532
1533// If file exists but not a regular file
1534if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
1535return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
1536}
1537
1538// If file does not exist
1539if (fileType == FileType.NO_EXIST) {
1540// If reading is to be ignored if file does not exist
1541if (ignoreNonExistentFile)
1542return null;
1543// Else return with error
1544else {
1545label += "file meant to be read";
1546return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
1547}
1548}
1549
1550if (charset == null) charset = Charset.defaultCharset();
1551
1552// Check if charset is supported
1553error = isCharsetSupported(charset);
1554if (error != null)
1555return error;
1556
1557FileInputStream fileInputStream = null;
1558BufferedReader bufferedReader = null;
1559try {
1560// Read text from file
1561fileInputStream = new FileInputStream(filePath);
1562bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream, charset));
1563
1564String receiveString;
1565
1566boolean firstLine = true;
1567while ((receiveString = bufferedReader.readLine()) != null ) {
1568if (!firstLine) dataStringBuilder.append("\n"); else firstLine = false;
1569dataStringBuilder.append(receiveString);
1570}
1571
1572Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("String", DataUtils.getTruncatedCommandOutput(dataStringBuilder.toString(), Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD, true, false, true), "-"));
1573} catch (Exception e) {
1574return FileUtilsErrno.ERRNO_READING_TEXT_FROM_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
1575} finally {
1576closeCloseable(fileInputStream);
1577closeCloseable(bufferedReader);
1578}
1579
1580return null;
1581}
1582
1583public static class ReadSerializableObjectResult {
1584public final Error error;
1585public final Serializable serializableObject;
1586
1587ReadSerializableObjectResult(Error error, Serializable serializableObject) {
1588this.error = error;
1589this.serializableObject = serializableObject;
1590}
1591}
1592
1593/**
1594* Read a {@link Serializable} object from file at path.
1595*
1596* @param label The optional label for file to read. This can optionally be {@code null}.
1597* @param filePath The {@code path} for file to read.
1598* @param readObjectType The {@link Class} of the object.
1599* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
1600* error if file to read doesn't exist.
1601* @return Returns the {@code error} if reading was not successful, otherwise {@code null}.
1602*/
1603@NonNull
1604public static <T extends Serializable> ReadSerializableObjectResult readSerializableObjectFromFile(String label, final String filePath, Class<T> readObjectType, final boolean ignoreNonExistentFile) {
1605label = (label == null || label.isEmpty() ? "" : label + " ");
1606if (filePath == null || filePath.isEmpty()) return new ReadSerializableObjectResult(FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "readSerializableObjectFromFile"), null);
1607
1608Logger.logVerbose(LOG_TAG, "Reading serializable object from " + label + "file at path \"" + filePath + "\"");
1609
1610T serializableObject;
1611
1612FileType fileType = getFileType(filePath, false);
1613
1614// If file exists but not a regular file
1615if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
1616return new ReadSerializableObjectResult(FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file"), null);
1617}
1618
1619// If file does not exist
1620if (fileType == FileType.NO_EXIST) {
1621// If reading is to be ignored if file does not exist
1622if (ignoreNonExistentFile)
1623return new ReadSerializableObjectResult(null, null);
1624// Else return with error
1625else {
1626label += "file meant to be read";
1627return new ReadSerializableObjectResult(FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label), null);
1628}
1629}
1630
1631FileInputStream fileInputStream = null;
1632ObjectInputStream objectInputStream = null;
1633try {
1634// Read serializable object from file
1635fileInputStream = new FileInputStream(filePath);
1636objectInputStream = new ObjectInputStream(fileInputStream);
1637//serializableObject = (T) objectInputStream.readObject();
1638serializableObject = readObjectType.cast(objectInputStream.readObject());
1639
1640//Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("String", DataUtils.getTruncatedCommandOutput(dataStringBuilder.toString(), Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD, true, false, true), "-"));
1641} catch (Exception e) {
1642return new ReadSerializableObjectResult(FileUtilsErrno.ERRNO_READING_SERIALIZABLE_OBJECT_TO_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage()), null);
1643} finally {
1644closeCloseable(fileInputStream);
1645closeCloseable(objectInputStream);
1646}
1647
1648return new ReadSerializableObjectResult(null, serializableObject);
1649}
1650
1651/**
1652* Write text {@code dataString} with a specific {@link Charset} to file at path.
1653*
1654* @param label The optional label for file to write. This can optionally be {@code null}.
1655* @param filePath The {@code path} for file to write.
1656* @param charset The {@link Charset} of the {@code dataString}. If this is {@code null},
1657* then default {@link Charset} will be used.
1658* @param dataString The data to write to file.
1659* @param append The {@code boolean} that decides if file should be appended to or not.
1660* @return Returns the {@code error} if writing was not successful, otherwise {@code null}.
1661*/
1662public static Error writeTextToFile(String label, final String filePath, Charset charset, final String dataString, final boolean append) {
1663label = (label == null || label.isEmpty() ? "" : label + " ");
1664if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "writeStringToFile");
1665
1666Logger.logVerbose(LOG_TAG, Logger.getMultiLineLogStringEntry("Writing text to " + label + "file at path \"" + filePath + "\"", DataUtils.getTruncatedCommandOutput(dataString, Logger.LOGGER_ENTRY_MAX_SAFE_PAYLOAD, true, false, true), "-"));
1667
1668Error error;
1669
1670error = preWriteToFile(label, filePath);
1671if (error != null)
1672return error;
1673
1674if (charset == null) charset = Charset.defaultCharset();
1675
1676// Check if charset is supported
1677error = isCharsetSupported(charset);
1678if (error != null)
1679return error;
1680
1681FileOutputStream fileOutputStream = null;
1682BufferedWriter bufferedWriter = null;
1683try {
1684// Write text to file
1685fileOutputStream = new FileOutputStream(filePath, append);
1686bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream, charset));
1687
1688bufferedWriter.write(dataString);
1689bufferedWriter.flush();
1690} catch (Exception e) {
1691return FileUtilsErrno.ERRNO_WRITING_TEXT_TO_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
1692} finally {
1693closeCloseable(fileOutputStream);
1694closeCloseable(bufferedWriter);
1695}
1696
1697return null;
1698}
1699
1700/**
1701* Write the {@link Serializable} {@code serializableObject} to file at path.
1702*
1703* @param label The optional label for file to write. This can optionally be {@code null}.
1704* @param filePath The {@code path} for file to write.
1705* @param serializableObject The object to write to file.
1706* @return Returns the {@code error} if writing was not successful, otherwise {@code null}.
1707*/
1708public static <T extends Serializable> Error writeSerializableObjectToFile(String label, final String filePath, final T serializableObject) {
1709label = (label == null || label.isEmpty() ? "" : label + " ");
1710if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "writeSerializableObjectToFile");
1711
1712Logger.logVerbose(LOG_TAG, "Writing serializable object to " + label + "file at path \"" + filePath + "\"");
1713
1714Error error;
1715
1716error = preWriteToFile(label, filePath);
1717if (error != null)
1718return error;
1719
1720FileOutputStream fileOutputStream = null;
1721ObjectOutputStream objectOutputStream = null;
1722try {
1723// Write serializable object to file
1724fileOutputStream = new FileOutputStream(filePath);
1725objectOutputStream = new ObjectOutputStream(fileOutputStream);
1726
1727objectOutputStream.writeObject(serializableObject);
1728objectOutputStream.flush();
1729} catch (Exception e) {
1730return FileUtilsErrno.ERRNO_WRITING_SERIALIZABLE_OBJECT_TO_FILE_FAILED_WITH_EXCEPTION.getError(e, label + "file", filePath, e.getMessage());
1731} finally {
1732closeCloseable(fileOutputStream);
1733closeCloseable(objectOutputStream);
1734}
1735
1736return null;
1737}
1738
1739private static Error preWriteToFile(String label, String filePath) {
1740Error error;
1741
1742FileType fileType = getFileType(filePath, false);
1743
1744// If file exists but not a regular file
1745if (fileType != FileType.NO_EXIST && fileType != FileType.REGULAR) {
1746return FileUtilsErrno.ERRNO_NON_REGULAR_FILE_FOUND.getError(label + "file", filePath).setLabel(label + "file");
1747}
1748
1749// Create the file parent directory
1750error = createParentDirectoryFile(label + "file parent", filePath);
1751if (error != null)
1752return error;
1753
1754return null;
1755}
1756
1757
1758
1759/**
1760* Check if a specific {@link Charset} is supported.
1761*
1762* @param charset The {@link Charset} to check.
1763* @return Returns the {@code error} if charset is not supported or failed to check it, otherwise {@code null}.
1764*/
1765public static Error isCharsetSupported(final Charset charset) {
1766if (charset == null) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError("charset", "isCharsetSupported");
1767
1768try {
1769if (!Charset.isSupported(charset.name())) {
1770return FileUtilsErrno.ERRNO_UNSUPPORTED_CHARSET.getError(charset.name());
1771}
1772} catch (Exception e) {
1773return FileUtilsErrno.ERRNO_CHECKING_IF_CHARSET_SUPPORTED_FAILED.getError(e, charset.name(), e.getMessage());
1774}
1775
1776return null;
1777}
1778
1779
1780
1781/**
1782* Close a {@link Closeable} object if not {@code null} and ignore any exceptions raised.
1783*
1784* @param closeable The {@link Closeable} object to close.
1785*/
1786public static void closeCloseable(final Closeable closeable) {
1787if (closeable != null) {
1788try {
1789closeable.close();
1790}
1791catch (IOException e) {
1792// ignore
1793}
1794}
1795}
1796
1797
1798
1799/**
1800* Set permissions for file at path. Existing permission outside the {@code permissionsToSet}
1801* will be removed.
1802*
1803* @param filePath The {@code path} for file to set permissions to.
1804* @param permissionsToSet The 3 character string that contains the "r", "w", "x" or "-" in-order.
1805*/
1806public static void setFilePermissions(final String filePath, final String permissionsToSet) {
1807setFilePermissions(null, filePath, permissionsToSet);
1808}
1809
1810/**
1811* Set permissions for file at path. Existing permission outside the {@code permissionsToSet}
1812* will be removed.
1813*
1814* @param label The optional label for the file. This can optionally be {@code null}.
1815* @param filePath The {@code path} for file to set permissions to.
1816* @param permissionsToSet The 3 character string that contains the "r", "w", "x" or "-" in-order.
1817*/
1818public static void setFilePermissions(String label, final String filePath, final String permissionsToSet) {
1819label = (label == null || label.isEmpty() ? "" : label + " ");
1820if (filePath == null || filePath.isEmpty()) return;
1821
1822if (!isValidPermissionString(permissionsToSet)) {
1823Logger.logError(LOG_TAG, "Invalid permissionsToSet passed to setFilePermissions: \"" + permissionsToSet + "\"");
1824return;
1825}
1826
1827File file = new File(filePath);
1828
1829if (permissionsToSet.contains("r")) {
1830if (!file.canRead()) {
1831Logger.logVerbose(LOG_TAG, "Setting read permissions for " + label + "file at path \"" + filePath + "\"");
1832file.setReadable(true);
1833}
1834} else {
1835if (file.canRead()) {
1836Logger.logVerbose(LOG_TAG, "Removing read permissions for " + label + "file at path \"" + filePath + "\"");
1837file.setReadable(false);
1838}
1839}
1840
1841
1842if (permissionsToSet.contains("w")) {
1843if (!file.canWrite()) {
1844Logger.logVerbose(LOG_TAG, "Setting write permissions for " + label + "file at path \"" + filePath + "\"");
1845file.setWritable(true);
1846}
1847} else {
1848if (file.canWrite()) {
1849Logger.logVerbose(LOG_TAG, "Removing write permissions for " + label + "file at path \"" + filePath + "\"");
1850file.setWritable(false);
1851}
1852}
1853
1854
1855if (permissionsToSet.contains("x")) {
1856if (!file.canExecute()) {
1857Logger.logVerbose(LOG_TAG, "Setting execute permissions for " + label + "file at path \"" + filePath + "\"");
1858file.setExecutable(true);
1859}
1860} else {
1861if (file.canExecute()) {
1862Logger.logVerbose(LOG_TAG, "Removing execute permissions for " + label + "file at path \"" + filePath + "\"");
1863file.setExecutable(false);
1864}
1865}
1866}
1867
1868
1869
1870/**
1871* Set missing permissions for file at path. Existing permission outside the {@code permissionsToSet}
1872* will not be removed.
1873*
1874* @param filePath The {@code path} for file to set permissions to.
1875* @param permissionsToSet The 3 character string that contains the "r", "w", "x" or "-" in-order.
1876*/
1877public static void setMissingFilePermissions(final String filePath, final String permissionsToSet) {
1878setMissingFilePermissions(null, filePath, permissionsToSet);
1879}
1880
1881/**
1882* Set missing permissions for file at path. Existing permission outside the {@code permissionsToSet}
1883* will not be removed.
1884*
1885* @param label The optional label for the file. This can optionally be {@code null}.
1886* @param filePath The {@code path} for file to set permissions to.
1887* @param permissionsToSet The 3 character string that contains the "r", "w", "x" or "-" in-order.
1888*/
1889public static void setMissingFilePermissions(String label, final String filePath, final String permissionsToSet) {
1890label = (label == null || label.isEmpty() ? "" : label + " ");
1891if (filePath == null || filePath.isEmpty()) return;
1892
1893if (!isValidPermissionString(permissionsToSet)) {
1894Logger.logError(LOG_TAG, "Invalid permissionsToSet passed to setMissingFilePermissions: \"" + permissionsToSet + "\"");
1895return;
1896}
1897
1898File file = new File(filePath);
1899
1900if (permissionsToSet.contains("r") && !file.canRead()) {
1901Logger.logVerbose(LOG_TAG, "Setting missing read permissions for " + label + "file at path \"" + filePath + "\"");
1902file.setReadable(true);
1903}
1904
1905if (permissionsToSet.contains("w") && !file.canWrite()) {
1906Logger.logVerbose(LOG_TAG, "Setting missing write permissions for " + label + "file at path \"" + filePath + "\"");
1907file.setWritable(true);
1908}
1909
1910if (permissionsToSet.contains("x") && !file.canExecute()) {
1911Logger.logVerbose(LOG_TAG, "Setting missing execute permissions for " + label + "file at path \"" + filePath + "\"");
1912file.setExecutable(true);
1913}
1914}
1915
1916
1917
1918/**
1919* Checking missing permissions for file at path.
1920*
1921* @param filePath The {@code path} for file to check permissions for.
1922* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
1923* @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission
1924* error is to be ignored.
1925* @return Returns the {@code error} if validating permissions failed, otherwise {@code null}.
1926*/
1927public static Error checkMissingFilePermissions(final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) {
1928return checkMissingFilePermissions(null, filePath, permissionsToCheck, ignoreIfNotExecutable);
1929}
1930
1931/**
1932* Checking missing permissions for file at path.
1933*
1934* @param label The optional label for the file. This can optionally be {@code null}.
1935* @param filePath The {@code path} for file to check permissions for.
1936* @param permissionsToCheck The 3 character string that contains the "r", "w", "x" or "-" in-order.
1937* @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission
1938* error is to be ignored.
1939* @return Returns the {@code error} if validating permissions failed, otherwise {@code null}.
1940*/
1941public static Error checkMissingFilePermissions(String label, final String filePath, final String permissionsToCheck, final boolean ignoreIfNotExecutable) {
1942label = (label == null || label.isEmpty() ? "" : label + " ");
1943if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "checkMissingFilePermissions");
1944
1945if (!isValidPermissionString(permissionsToCheck)) {
1946Logger.logError(LOG_TAG, "Invalid permissionsToCheck passed to checkMissingFilePermissions: \"" + permissionsToCheck + "\"");
1947return FileUtilsErrno.ERRNO_INVALID_FILE_PERMISSIONS_STRING_TO_CHECK.getError();
1948}
1949
1950File file = new File(filePath);
1951
1952// If file is not readable
1953if (permissionsToCheck.contains("r") && !file.canRead()) {
1954return FileUtilsErrno.ERRNO_FILE_NOT_READABLE.getError(label + "file", filePath).setLabel(label + "file");
1955}
1956
1957// If file is not writable
1958if (permissionsToCheck.contains("w") && !file.canWrite()) {
1959return FileUtilsErrno.ERRNO_FILE_NOT_WRITABLE.getError(label + "file", filePath).setLabel(label + "file");
1960}
1961// If file is not executable
1962// This canExecute() will give "avc: granted { execute }" warnings for target sdk 29
1963else if (permissionsToCheck.contains("x") && !file.canExecute() && !ignoreIfNotExecutable) {
1964return FileUtilsErrno.ERRNO_FILE_NOT_EXECUTABLE.getError(label + "file", filePath).setLabel(label + "file");
1965}
1966
1967return null;
1968}
1969
1970
1971
1972/**
1973* Checks whether string exactly matches the 3 character permission string that
1974* contains the "r", "w", "x" or "-" in-order.
1975*
1976* @param string The {@link String} to check.
1977* @return Returns {@code true} if string exactly matches a permission string, otherwise {@code false}.
1978*/
1979public static boolean isValidPermissionString(final String string) {
1980if (string == null || string.isEmpty()) return false;
1981return Pattern.compile("^([r-])[w-][x-]$", 0).matcher(string).matches();
1982}
1983
1984
1985
1986/**
1987* Get a {@link Error} that contains a shorter version of {@link Errno} message.
1988*
1989* @param error The original {@link Error} returned by one of the {@link FileUtils} functions.
1990* @return Returns the shorter {@link Error} if one exists, otherwise original {@code error}.
1991*/
1992public static Error getShortFileUtilsError(final Error error) {
1993String type = error.getType();
1994if (!FileUtilsErrno.TYPE.equals(type)) return error;
1995
1996Errno shortErrno = FileUtilsErrno.ERRNO_SHORT_MAPPING.get(Errno.valueOf(type, error.getCode()));
1997if (shortErrno == null) return error;
1998
1999List<Throwable> throwables = error.getThrowablesList();
2000if (throwables.isEmpty())
2001return shortErrno.getError(DataUtils.getDefaultIfNull(error.getLabel(), "file"));
2002else
2003return shortErrno.getError(throwables, error.getLabel(), "file");
2004}
2005
2006
2007/**
2008* Get file dirname for file at {@code filePath}.
2009*
2010* @param filePath The {@code path} for file.
2011* @return Returns the file dirname if not {@code null}.
2012*/
2013public static String getFileDirname(String filePath) {
2014if (DataUtils.isNullOrEmpty(filePath)) return null;
2015int lastSlash = filePath.lastIndexOf('/');
2016return (lastSlash == -1) ? null : filePath.substring(0, lastSlash);
2017}
2018
2019/**
2020* Get file basename for file at {@code filePath}.
2021*
2022* @param filePath The {@code path} for file.
2023* @return Returns the file basename if not {@code null}.
2024*/
2025public static String getFileBasename(String filePath) {
2026if (DataUtils.isNullOrEmpty(filePath)) return null;
2027int lastSlash = filePath.lastIndexOf('/');
2028return (lastSlash == -1) ? filePath : filePath.substring(lastSlash + 1);
2029}
2030
2031/**
2032* Get file basename for file at {@code filePath} without extension.
2033*
2034* @param filePath The {@code path} for file.
2035* @return Returns the file basename without extension if not {@code null}.
2036*/
2037public static String getFileBasenameWithoutExtension(String filePath) {
2038String fileBasename = getFileBasename(filePath);
2039if (DataUtils.isNullOrEmpty(fileBasename)) return null;
2040int lastDot = fileBasename.lastIndexOf('.');
2041return (lastDot == -1) ? fileBasename : fileBasename.substring(0, lastDot);
2042}
2043
2044}
2045