• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.util;
17 
18 import com.android.tradefed.command.FatalHostError;
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.error.HarnessIOException;
21 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
22 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.result.LogDataType;
25 import com.android.tradefed.result.error.InfraErrorIdentifier;
26 import com.android.tradefed.testtype.IAbi;
27 
28 import com.google.common.collect.ImmutableSet;
29 
30 import java.io.BufferedInputStream;
31 import java.io.BufferedOutputStream;
32 import java.io.ByteArrayInputStream;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileNotFoundException;
36 import java.io.FileOutputStream;
37 import java.io.FileWriter;
38 import java.io.FilenameFilter;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.nio.file.FileAlreadyExistsException;
43 import java.nio.file.FileSystemException;
44 import java.nio.file.FileVisitOption;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.nio.file.Paths;
48 import java.nio.file.attribute.PosixFilePermission;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.EnumSet;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.LinkedHashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.stream.Stream;
60 import java.util.zip.ZipFile;
61 /**
62  * A helper class for file related operations
63  */
64 public class FileUtil {
65 
66     private static final ImmutableSet<String> DISK_SPACE_ERRORS =
67             ImmutableSet.of("No space left on device");
68 
69     /**
70      * The minimum allowed disk space in megabytes. File creation methods will throw
71      * {@link LowDiskSpaceException} if the usable disk space in desired partition is less than
72      * this amount.
73      */
74     @Option(name = "min-disk-space", description = "The minimum allowed disk"
75         + " space in megabytes for file-creation methods. May be set to"
76         + " 0 to disable checking.")
77     private static long mMinDiskSpaceMb = 100;
78 
79     private static final char[] SIZE_SPECIFIERS = {
80             ' ', 'K', 'M', 'G', 'T'
81     };
82 
83     private static String sChmod = "chmod";
84 
85     /** A map of {@link PosixFilePermission} to its corresponding Unix file mode */
86     private static final Map<PosixFilePermission, Integer> PERM_MODE_MAP = new HashMap<>();
87     static {
PERM_MODE_MAP.put(PosixFilePermission.OWNER_READ, 0b100000000)88         PERM_MODE_MAP.put(PosixFilePermission.OWNER_READ,     0b100000000);
PERM_MODE_MAP.put(PosixFilePermission.OWNER_WRITE, 0b010000000)89         PERM_MODE_MAP.put(PosixFilePermission.OWNER_WRITE,    0b010000000);
PERM_MODE_MAP.put(PosixFilePermission.OWNER_EXECUTE, 0b001000000)90         PERM_MODE_MAP.put(PosixFilePermission.OWNER_EXECUTE,  0b001000000);
PERM_MODE_MAP.put(PosixFilePermission.GROUP_READ, 0b000100000)91         PERM_MODE_MAP.put(PosixFilePermission.GROUP_READ,     0b000100000);
PERM_MODE_MAP.put(PosixFilePermission.GROUP_WRITE, 0b000010000)92         PERM_MODE_MAP.put(PosixFilePermission.GROUP_WRITE,    0b000010000);
PERM_MODE_MAP.put(PosixFilePermission.GROUP_EXECUTE, 0b000001000)93         PERM_MODE_MAP.put(PosixFilePermission.GROUP_EXECUTE,  0b000001000);
PERM_MODE_MAP.put(PosixFilePermission.OTHERS_READ, 0b000000100)94         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_READ,    0b000000100);
PERM_MODE_MAP.put(PosixFilePermission.OTHERS_WRITE, 0b000000010)95         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_WRITE,   0b000000010);
PERM_MODE_MAP.put(PosixFilePermission.OTHERS_EXECUTE, 0b000000001)96         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_EXECUTE, 0b000000001);
97     }
98 
99     public static final int FILESYSTEM_FILENAME_MAX_LENGTH = 255;
100 
101     /**
102      * Exposed for testing. Allows to modify the chmod binary name we look for, in order to tests
103      * system with no chmod support.
104      */
setChmodBinary(String chmodName)105     protected static void setChmodBinary(String chmodName) {
106         sChmod = chmodName;
107     }
108 
109     /**
110      * Thrown if usable disk space is below minimum threshold.
111      */
112     @SuppressWarnings("serial")
113     public static class LowDiskSpaceException extends FatalHostError {
114 
LowDiskSpaceException(String msg, Throwable cause)115         LowDiskSpaceException(String msg, Throwable cause) {
116             super(msg, cause, InfraErrorIdentifier.LAB_HOST_FILESYSTEM_FULL);
117         }
118 
LowDiskSpaceException(String msg)119         LowDiskSpaceException(String msg) {
120             super(msg, InfraErrorIdentifier.LAB_HOST_FILESYSTEM_FULL);
121         }
122 
123     }
124 
125     /**
126      * Method to create a chain of directories, and set them all group execute/read/writable as they
127      * are created, by calling {@link #chmodGroupRWX(File)}.  Essentially a version of
128      * {@link File#mkdirs()} that also runs {@link #chmod(File, String)}.
129      *
130      * @param file the name of the directory to create, possibly with containing directories that
131      *        don't yet exist.
132      * @return {@code true} if {@code file} exists and is a directory, {@code false} otherwise.
133      */
mkdirsRWX(File file)134     public static boolean mkdirsRWX(File file) {
135         File parent = file.getParentFile();
136 
137         if (parent != null && !parent.isDirectory()) {
138             // parent doesn't exist.  recurse upward, which should both mkdir and chmod
139             if (!mkdirsRWX(parent)) {
140                 // Couldn't mkdir parent, fail
141                 CLog.w("Failed to mkdir parent dir %s.", parent);
142                 return false;
143             }
144         }
145 
146         // by this point the parent exists.  Try to mkdir file
147         if (file.isDirectory() || file.mkdir()) {
148             // file should exist.  Try chmod and complain if that fails, but keep going
149             boolean setPerms = chmodGroupRWX(file);
150             if (!setPerms) {
151                 CLog.w("Failed to set dir %s to be group accessible.", file);
152             }
153         }
154 
155         return file.isDirectory();
156     }
157 
chmodRWXRecursively(File file)158     public static boolean chmodRWXRecursively(File file) {
159         boolean success = true;
160         if (!file.setExecutable(true, false)) {
161             CLog.w("Failed to set %s executable.", file.getAbsolutePath());
162             success = false;
163         }
164         if (!file.setWritable(true, false)) {
165             CLog.w("Failed to set %s writable.", file.getAbsolutePath());
166             success = false;
167         }
168         if (!file.setReadable(true, false)) {
169             CLog.w("Failed to set %s readable", file.getAbsolutePath());
170             success = false;
171         }
172 
173         if (file.isDirectory()) {
174             File[] children = file.listFiles();
175             for (File child : children) {
176                 if (!chmodRWXRecursively(child)) {
177                     success = false;
178                 }
179             }
180 
181         }
182         return success;
183     }
184 
chmod(File file, String perms)185     public static boolean chmod(File file, String perms) {
186         // No need to print, runUtil already prints the command
187         CommandResult result =
188                 RunUtil.getDefault().runTimedCmd(10 * 1000, sChmod, perms, file.getAbsolutePath());
189         return result.getStatus().equals(CommandStatus.SUCCESS);
190     }
191 
192     /**
193      * Performs a best effort attempt to make given file group readable and writable.
194      * <p/>
195      * Note that the execute permission is required to make directories accessible.  See
196      * {@link #chmodGroupRWX(File)}.
197      * <p/>
198      * If 'chmod' system command is not supported by underlying OS, will set file to writable by
199      * all.
200      *
201      * @param file the {@link File} to make owner and group writable
202      * @return <code>true</code> if file was successfully made group writable, <code>false</code>
203      *         otherwise
204      */
chmodGroupRW(File file)205     public static boolean chmodGroupRW(File file) {
206         if (chmodExists()) {
207             if (chmod(file, "ug+rw")) {
208                 return true;
209             } else {
210                 CLog.d("Failed chmod on %s", file.getAbsolutePath());
211                 return false;
212             }
213         } else {
214             CLog.d("chmod not available; attempting to set %s globally RW", file.getAbsolutePath());
215             return file.setWritable(true, false /* false == writable for all */) &&
216                     file.setReadable(true, false /* false == readable for all */);
217         }
218     }
219 
220     /**
221      * Performs a best effort attempt to ensure given file group executable, readable, and writable.
222      *
223      * <p>If 'chmod' system command is not supported by underlying OS, will attempt to set
224      * permissions for all users. The operation is synchronized to prevent race condition introduced
225      * by accessing files from a cache, e.g., GCSFileDownloader.
226      *
227      * @param file the {@link File} to make owner and group writable
228      * @return <code>true</code> if permissions were set successfully, <code>false</code> otherwise
229      */
ensureGroupRWX(File file)230     public static synchronized boolean ensureGroupRWX(File file) {
231         if (!file.canExecute()) {
232             // Set the executable bit if needed
233             return chmodGroupRWX(file);
234         }
235         return true;
236     }
237 
238     /**
239      * Performs a best effort attempt to make given file group executable, readable, and writable.
240      * <p/>
241      * If 'chmod' system command is not supported by underlying OS, will attempt to set permissions
242      * for all users.
243      *
244      * @param file the {@link File} to make owner and group writable
245      * @return <code>true</code> if permissions were set successfully, <code>false</code> otherwise
246      */
chmodGroupRWX(File file)247     public static boolean chmodGroupRWX(File file) {
248         if (chmodExists()) {
249             if (chmod(file, "ug+rwx")) {
250                 return true;
251             } else {
252                 CLog.d("Failed chmod on %s", file.getAbsolutePath());
253                 return false;
254             }
255         } else {
256             CLog.d(
257                     "chmod not available; attempting to set %s globally RWX",
258                     file.getAbsolutePath());
259             return file.setExecutable(true, false /* false == executable for all */) &&
260                     file.setWritable(true, false /* false == writable for all */) &&
261                     file.setReadable(true, false /* false == readable for all */);
262         }
263     }
264 
265     /**
266      * Internal helper to determine if 'chmod' is available on the system OS.
267      */
chmodExists()268     protected static boolean chmodExists() {
269         // Silence the scary process exception when chmod is missing, we will log instead.
270         CommandResult result = RunUtil.getDefault().runTimedCmdSilently(10 * 1000, sChmod);
271         // Exit code 127 means “command not found”. 88 is our internal error
272         if (result.getExitCode() != null
273                 && result.getExitCode() != 127
274                 && result.getExitCode() != 88) {
275             return true;
276         }
277         CLog.w("Chmod is not supported by this OS.");
278         return false;
279     }
280 
281     /**
282      * Recursively set read and exec (if folder) permissions for given file.
283      */
setReadableRecursive(File file)284     public static void setReadableRecursive(File file) {
285         file.setReadable(true);
286         if (file.isDirectory()) {
287             file.setExecutable(true);
288             File[] children = file.listFiles();
289             if (children != null) {
290                 for (File childFile : file.listFiles()) {
291                     setReadableRecursive(childFile);
292                 }
293             }
294         }
295     }
296 
297     /**
298      * Helper function to create a temp directory in the system default temporary file directory.
299      *
300      * @param prefix The prefix string to be used in generating the file's name; must be at least
301      *            three characters long
302      * @return the created directory
303      * @throws IOException if file could not be created
304      */
createTempDir(String prefix)305     public static File createTempDir(String prefix) throws IOException {
306         return createTempDir(prefix, null);
307     }
308 
309     /**
310      * Helper function to create a temp directory.
311      *
312      * @param prefix The prefix string to be used in generating the file's name; must be at least
313      *            three characters long
314      * @param parentDir The parent directory in which the directory is to be created. If
315      *            <code>null</code> the system default temp directory will be used.
316      * @return the created directory
317      * @throws IOException if file could not be created
318      */
createTempDir(String prefix, File parentDir)319     public static File createTempDir(String prefix, File parentDir) throws IOException {
320         // create a temp file with unique name, then make it a directory
321         if (parentDir != null) {
322             CLog.d("Creating temp directory at %s with prefix \"%s\"",
323               parentDir.getAbsolutePath(), prefix);
324         }
325         File tmpDir = File.createTempFile(prefix, "", parentDir);
326         return deleteFileAndCreateDirWithSameName(tmpDir);
327     }
328 
deleteFileAndCreateDirWithSameName(File tmpDir)329     private static File deleteFileAndCreateDirWithSameName(File tmpDir) throws IOException {
330         tmpDir.delete();
331         return createDir(tmpDir);
332     }
333 
createDir(File tmpDir)334     private static File createDir(File tmpDir) throws IOException {
335         if (!tmpDir.mkdirs()) {
336             throw new IOException("unable to create directory");
337         }
338         return tmpDir;
339     }
340 
341     /**
342      * Helper function to create a named directory inside your temp folder.
343      * <p/>
344      * This directory will not have its name randomized. If the directory already exists it will
345      * be returned.
346      *
347      * @param name The name of the directory to create in your tmp folder.
348      * @return the created directory
349      */
createNamedTempDir(String name)350     public static File createNamedTempDir(String name) throws IOException {
351         File namedTmpDir = new File(System.getProperty("java.io.tmpdir"), name);
352         if (!namedTmpDir.exists()) {
353             createDir(namedTmpDir);
354         }
355         return namedTmpDir;
356     }
357 
358     /**
359      * Helper function to create a named directory inside a foldere.
360      *
361      * <p>This directory will not have its name randomized. If the directory already exists it will
362      * be returned.
363      *
364      * @param parentDir the directory where to create the dir. If null, will be in /tmp
365      * @param name The name of the directory to create in the parent folder
366      * @return the created directory
367      */
createNamedTempDir(File parentDir, String name)368     public static File createNamedTempDir(File parentDir, String name) throws IOException {
369         String parentPath;
370         if (parentDir == null) {
371             parentPath = System.getProperty("java.io.tmpdir");
372         } else {
373             parentPath = parentDir.getAbsolutePath();
374         }
375         File namedTmpDir = new File(parentPath, name);
376         if (!namedTmpDir.exists()) {
377             createDir(namedTmpDir);
378         }
379         return namedTmpDir;
380     }
381 
382     /**
383      * Helper wrapper function around {@link File#createTempFile(String, String)} that audits for
384      * potential out of disk space scenario.
385      *
386      * @see File#createTempFile(String, String)
387      * @throws LowDiskSpaceException if disk space on temporary partition is lower than minimum
388      *             allowed
389      */
createTempFile(String prefix, String suffix)390     public static File createTempFile(String prefix, String suffix) throws IOException {
391         return internalCreateTempFile(prefix, suffix, null);
392     }
393 
394     /**
395      * Helper wrapper function around {@link File#createTempFile(String, String, File)}
396      * that audits for potential out of disk space scenario.
397      *
398      * @see File#createTempFile(String, String, File)
399      * @throws LowDiskSpaceException if disk space on partition is lower than minimum allowed
400      */
createTempFile(String prefix, String suffix, File parentDir)401     public static File createTempFile(String prefix, String suffix, File parentDir)
402             throws IOException {
403         return internalCreateTempFile(prefix, suffix, parentDir);
404     }
405 
406     /**
407      * Internal helper to create a temporary file.
408      */
internalCreateTempFile(String prefix, String suffix, File parentDir)409     private static File internalCreateTempFile(String prefix, String suffix, File parentDir)
410             throws IOException {
411         // File.createTempFile add an additional random long in the name so we remove the length.
412         int overflowLength = prefix.length() + 19 - FILESYSTEM_FILENAME_MAX_LENGTH;
413         if (suffix != null) {
414             // suffix may be null
415             overflowLength += suffix.length();
416         }
417         if (overflowLength > 0) {
418             CLog.w("Filename for prefix: %s and suffix: %s, would be too long for FileSystem,"
419                     + "truncating it.", prefix, suffix);
420             // We truncate from suffix in priority because File.createTempFile wants prefix to be
421             // at least 3 characters.
422             if (suffix.length() >= overflowLength) {
423                 int temp = overflowLength;
424                 overflowLength -= suffix.length();
425                 suffix = suffix.substring(temp, suffix.length());
426             } else {
427                 overflowLength -= suffix.length();
428                 suffix = "";
429             }
430             if (overflowLength > 0) {
431                 // Whatever remaining to remove after suffix has been truncating should be inside
432                 // prefix, otherwise there would not be overflow.
433                 prefix = prefix.substring(0, prefix.length() - overflowLength);
434             }
435         }
436         File returnFile = null;
437         if (parentDir != null) {
438             CLog.d("Creating temp file at %s with prefix \"%s\" suffix \"%s\"",
439                     parentDir.getAbsolutePath(), prefix, suffix);
440         }
441         try {
442             returnFile = File.createTempFile(prefix, suffix, parentDir);
443         } catch (IOException e) {
444             throw new HarnessIOException(e, InfraErrorIdentifier.LAB_HOST_FILESYSTEM_ERROR);
445         }
446         verifyDiskSpace(returnFile);
447         return returnFile;
448     }
449 
450     /**
451      * A helper method that hardlinks a file to another file. Fallback to copy in case of cross
452      * partition linking.
453      *
454      * @param origFile the original file
455      * @param destFile the destination file
456      * @throws IOException if failed to hardlink file
457      */
hardlinkFile(File origFile, File destFile)458     public static void hardlinkFile(File origFile, File destFile) throws IOException {
459         hardlinkFile(origFile, destFile, false);
460     }
461 
462     /**
463      * A helper method that hardlinks a file to another file. Fallback to copy in case of cross
464      * partition linking.
465      *
466      * @param origFile the original file
467      * @param destFile the destination file
468      * @param ignoreExistingFile If True and the file being linked already exists, skip the
469      *     exception.
470      * @throws IOException if failed to hardlink file
471      */
hardlinkFile(File origFile, File destFile, boolean ignoreExistingFile)472     public static void hardlinkFile(File origFile, File destFile, boolean ignoreExistingFile)
473             throws IOException {
474         try {
475             Files.createLink(destFile.toPath(), origFile.toPath());
476         } catch (FileAlreadyExistsException e) {
477             if (!ignoreExistingFile) {
478                 throw e;
479             }
480         } catch (FileSystemException e) {
481             if (e.getMessage().contains("Invalid cross-device link")) {
482                 CLog.d("Hardlink failed: '%s', falling back to copy.", e.getMessage());
483                 copyFile(origFile, destFile);
484                 return;
485             }
486             throw e;
487         }
488     }
489 
490     /**
491      * A helper method that symlinks a file to another file
492      *
493      * @param origFile the original file
494      * @param destFile the destination file
495      * @throws IOException if failed to symlink file
496      */
symlinkFile(File origFile, File destFile)497     public static void symlinkFile(File origFile, File destFile) throws IOException {
498         CLog.d(
499                 "Attempting symlink from %s to %s",
500                 origFile.getAbsolutePath(), destFile.getAbsolutePath());
501         Files.createSymbolicLink(destFile.toPath(), origFile.toPath());
502     }
503 
504     /**
505      * Recursively hardlink folder contents.
506      * <p/>
507      * Only supports copying of files and directories - symlinks are not copied. If the destination
508      * directory does not exist, it will be created.
509      *
510      * @param sourceDir the folder that contains the files to copy
511      * @param destDir the destination folder
512      * @throws IOException
513      */
recursiveHardlink(File sourceDir, File destDir)514     public static void recursiveHardlink(File sourceDir, File destDir) throws IOException {
515         recursiveHardlink(sourceDir, destDir, false);
516     }
517 
518     /**
519      * Recursively hardlink folder contents.
520      *
521      * <p>Only supports copying of files and directories - symlinks are not copied. If the
522      * destination directory does not exist, it will be created.
523      *
524      * @param sourceDir the folder that contains the files to copy
525      * @param destDir the destination folder
526      * @param ignoreExistingFile If True and the file being linked already exists, skip the
527      *     exception.
528      * @throws IOException
529      */
recursiveHardlink(File sourceDir, File destDir, boolean ignoreExistingFile)530     public static void recursiveHardlink(File sourceDir, File destDir, boolean ignoreExistingFile)
531             throws IOException {
532         recursiveHardlink(sourceDir, destDir, ignoreExistingFile, new HashSet<>());
533     }
534 
535     /**
536      * Recursively hardlink folder contents.
537      *
538      * <p>Only supports copying of files and directories - symlinks are not copied. If the
539      * destination directory does not exist, it will be created.
540      *
541      * @param sourceDir the folder that contains the files to copy
542      * @param destDir the destination folder
543      * @param ignoreExistingFile If True and the file being linked already exists, skip the
544      *     exception.
545      * @param copyInsteadofHardlink Set of files that needs to be copied instead of linked.
546      * @throws IOException
547      */
recursiveHardlink( File sourceDir, File destDir, boolean ignoreExistingFile, Set<String> copyInsteadofHardlink)548     public static void recursiveHardlink(
549             File sourceDir,
550             File destDir,
551             boolean ignoreExistingFile,
552             Set<String> copyInsteadofHardlink)
553             throws IOException {
554         if (!destDir.isDirectory() && !destDir.mkdir()) {
555             throw new IOException(String.format("Could not create directory %s",
556                     destDir.getAbsolutePath()));
557         }
558         for (File childFile : sourceDir.listFiles()) {
559             File destChild = new File(destDir, childFile.getName());
560             if (childFile.isDirectory()) {
561                 recursiveHardlink(childFile, destChild, ignoreExistingFile);
562             } else if (childFile.isFile()) {
563                 if (copyInsteadofHardlink.contains(childFile.getName())) {
564                     FileUtil.copyFile(childFile, destChild);
565                 } else {
566                     hardlinkFile(childFile, destChild, ignoreExistingFile);
567                 }
568             }
569         }
570     }
571 
572     /**
573      * Recursively symlink folder contents.
574      *
575      * <p>Only supports copying of files and directories - symlinks are not copied. If the
576      * destination directory does not exist, it will be created.
577      *
578      * @param sourceDir the folder that contains the files to copy
579      * @param destDir the destination folder
580      * @throws IOException
581      */
recursiveSymlink(File sourceDir, File destDir)582     public static void recursiveSymlink(File sourceDir, File destDir) throws IOException {
583         if (!destDir.isDirectory() && !destDir.mkdir()) {
584             throw new IOException(
585                     String.format("Could not create directory %s", destDir.getAbsolutePath()));
586         }
587         for (File childFile : sourceDir.listFiles()) {
588             File destChild = new File(destDir, childFile.getName());
589             if (childFile.isDirectory()) {
590                 recursiveSymlink(childFile, destChild);
591             } else if (childFile.isFile()) {
592                 symlinkFile(childFile, destChild);
593             }
594         }
595     }
596 
597     /**
598      * A helper method that copies a file's contents to a local file
599      *
600      * @param origFile the original file to be copied
601      * @param destFile the destination file
602      * @throws IOException if failed to copy file
603      */
copyFile(File origFile, File destFile)604     public static void copyFile(File origFile, File destFile) throws IOException {
605         writeToFile(new FileInputStream(origFile), destFile);
606     }
607 
608     /**
609      * Recursively copy folder contents.
610      * <p/>
611      * Only supports copying of files and directories - symlinks are not copied. If the destination
612      * directory does not exist, it will be created.
613      *
614      * @param sourceDir the folder that contains the files to copy
615      * @param destDir the destination folder
616      * @throws IOException
617      */
recursiveCopy(File sourceDir, File destDir)618     public static void recursiveCopy(File sourceDir, File destDir) throws IOException {
619         File[] childFiles = sourceDir.listFiles();
620         if (childFiles == null) {
621             throw new IOException(String.format(
622                     "Failed to recursively copy. Could not determine contents for directory '%s'",
623                     sourceDir.getAbsolutePath()));
624         }
625         if (!destDir.isDirectory() && !destDir.mkdir()) {
626             throw new IOException(String.format("Could not create directory %s",
627                 destDir.getAbsolutePath()));
628         }
629         for (File childFile : childFiles) {
630             File destChild = new File(destDir, childFile.getName());
631             if (childFile.isDirectory()) {
632                 recursiveCopy(childFile, destChild);
633             } else if (childFile.isFile()) {
634                 copyFile(childFile, destChild);
635             }
636         }
637     }
638 
639     /**
640      * A helper method for reading string data from a file
641      *
642      * @param sourceFile the file to read from
643      * @throws IOException
644      * @throws FileNotFoundException
645      */
readStringFromFile(File sourceFile)646     public static String readStringFromFile(File sourceFile) throws IOException {
647         return readStringFromFile(sourceFile, 0, 0);
648     }
649 
650     /**
651      * A helper method for reading partial string data from a file
652      *
653      * @param sourceFile the file to read from
654      * @param startOffset the start offset to read from the file.
655      * @param length the number of bytes to read of the file.
656      * @throws IOException
657      * @throws FileNotFoundException
658      */
readStringFromFile(File sourceFile, long startOffset, long length)659     public static String readStringFromFile(File sourceFile, long startOffset, long length)
660             throws IOException {
661         try (FileInputStream is = new FileInputStream(sourceFile)) {
662             if (startOffset < 0) {
663                 startOffset = 0;
664             }
665             long fileLength = sourceFile.length();
666             is.skip(startOffset);
667             if (length <= 0 || fileLength <= startOffset + length) {
668                 return StreamUtil.getStringFromStream(is);
669             }
670             return StreamUtil.getStringFromStream(is, length);
671         }
672     }
673 
674     /**
675      * A helper method for writing string data to file
676      *
677      * @param inputString the input {@link String}
678      * @param destFile the destination file to write to
679      */
writeToFile(String inputString, File destFile)680     public static void writeToFile(String inputString, File destFile) throws IOException {
681         writeToFile(inputString, destFile, false);
682     }
683 
684     /**
685      * A helper method for writing or appending string data to file
686      *
687      * @param inputString the input {@link String}
688      * @param destFile the destination file to write or append to
689      * @param append append to end of file if true, overwrite otherwise
690      */
writeToFile(String inputString, File destFile, boolean append)691     public static void writeToFile(String inputString, File destFile, boolean append)
692             throws IOException {
693         writeToFile(new ByteArrayInputStream(inputString.getBytes()), destFile, append);
694     }
695 
696     /**
697      * A helper method for writing stream data to file
698      *
699      * @param input the unbuffered input stream
700      * @param destFile the destination file to write to
701      */
writeToFile(InputStream input, File destFile)702     public static void writeToFile(InputStream input, File destFile) throws IOException {
703         writeToFile(input, destFile, false);
704     }
705 
706     /**
707      * A helper method for writing stream data to file
708      *
709      * @param input the unbuffered input stream
710      * @param destFile the destination file to write or append to
711      * @param append append to end of file if true, overwrite otherwise
712      */
writeToFile( InputStream input, File destFile, boolean append)713     public static void writeToFile(
714             InputStream input, File destFile, boolean append) throws IOException {
715         // Set size to a negative value to write all content starting at the given offset.
716         writeToFile(input, destFile, append, 0, -1);
717     }
718 
719     /**
720      * A helper method for writing stream data to file
721      *
722      * @param input the unbuffered input stream
723      * @param destFile the destination file to write or append to
724      * @param append append to end of file if true, overwrite otherwise
725      * @param startOffset the start offset of the input stream to retrieve data
726      * @param size number of bytes to retrieve from the input stream, set it to a negative value to
727      *     retrieve all content starting at the given offset.
728      */
writeToFile( InputStream input, File destFile, boolean append, long startOffset, long size)729     public static void writeToFile(
730             InputStream input, File destFile, boolean append, long startOffset, long size)
731             throws IOException {
732         InputStream origStream = null;
733         OutputStream destStream = null;
734         try {
735             origStream = new BufferedInputStream(input);
736             destStream = new BufferedOutputStream(new FileOutputStream(destFile, append));
737             StreamUtil.copyStreams(origStream, destStream, startOffset, size);
738         } finally {
739             StreamUtil.close(origStream);
740             StreamUtil.flushAndCloseStream(destStream);
741         }
742     }
743 
744     /**
745      * Note: We should never use CLog in here, since it also relies on that method, this would lead
746      * to infinite recursion.
747      */
verifyDiskSpace(File file)748     private static void verifyDiskSpace(File file) {
749         // Based on empirical testing File.getUsableSpace is a low cost operation (~ 100 us for
750         // local disk, ~ 100 ms for network disk). Therefore call it every time tmp file is
751         // created
752         long usableSpace = 0L;
753         File toCheck = file;
754         if (!file.isDirectory() && file.getParentFile() != null) {
755             // If the given file is not a directory it might not work properly so using the parent
756             // in that case.
757             toCheck = file.getParentFile();
758         }
759         usableSpace = toCheck.getUsableSpace();
760 
761         long minDiskSpace = mMinDiskSpaceMb * 1024 * 1024;
762         if (usableSpace < minDiskSpace) {
763             String message =
764                     String.format(
765                             "Available space on %s is %.2f MB. Min is %d MB.",
766                             toCheck.getAbsolutePath(),
767                             usableSpace / (1024.0 * 1024.0),
768                             mMinDiskSpaceMb);
769             throw new LowDiskSpaceException(message);
770         }
771     }
772 
773     /**
774      * Recursively delete given file or directory and all its contents.
775      *
776      * @param rootDir the directory or file to be deleted; can be null
777      */
recursiveDelete(File rootDir)778     public static void recursiveDelete(File rootDir) {
779         if (rootDir != null) {
780             // We expand directories if they are not symlink
781             if (rootDir.isDirectory() && !Files.isSymbolicLink(rootDir.toPath())) {
782                 File[] childFiles = rootDir.listFiles();
783                 if (childFiles != null) {
784                     for (File child : childFiles) {
785                         recursiveDelete(child);
786                     }
787                 }
788             }
789             rootDir.delete();
790         }
791     }
792 
793     /**
794      * Gets the extension for given file name.
795      *
796      * @param fileName
797      * @return the extension or empty String if file has no extension
798      */
getExtension(String fileName)799     public static String getExtension(String fileName) {
800         int index = fileName.lastIndexOf('.');
801         if (index == -1) {
802             return "";
803         } else {
804             return fileName.substring(index);
805         }
806     }
807 
808     /**
809      * Gets the base name, without extension, of given file name.
810      * <p/>
811      * e.g. getBaseName("file.txt") will return "file"
812      *
813      * @param fileName
814      * @return the base name
815      */
getBaseName(String fileName)816     public static String getBaseName(String fileName) {
817         int index = fileName.lastIndexOf('.');
818         if (index == -1) {
819             return fileName;
820         } else {
821             return fileName.substring(0, index);
822         }
823     }
824 
825     /**
826      * Utility method to do byte-wise content comparison of two files.
827      *
828      * @return <code>true</code> if file contents are identical
829      */
compareFileContents(File file1, File file2)830     public static boolean compareFileContents(File file1, File file2) throws IOException {
831         BufferedInputStream stream1 = null;
832         BufferedInputStream stream2 = null;
833 
834         boolean result = true;
835         try {
836             stream1 = new BufferedInputStream(new FileInputStream(file1));
837             stream2 = new BufferedInputStream(new FileInputStream(file2));
838             boolean eof = false;
839             while (!eof) {
840                 int byte1 = stream1.read();
841                 int byte2 = stream2.read();
842                 if (byte1 != byte2) {
843                     result = false;
844                     break;
845                 }
846                 eof = byte1 == -1;
847             }
848         } finally {
849             StreamUtil.close(stream1);
850             StreamUtil.close(stream2);
851         }
852         return result;
853     }
854 
855     /**
856      * Helper method which constructs a unique file on temporary disk, whose name corresponds as
857      * closely as possible to the file name given by the remote file path
858      *
859      * @param remoteFilePath the '/' separated remote path to construct the name from
860      * @param parentDir the parent directory to create the file in. <code>null</code> to use the
861      * default temporary directory
862      */
createTempFileForRemote(String remoteFilePath, File parentDir)863     public static File createTempFileForRemote(String remoteFilePath, File parentDir)
864             throws IOException {
865         String[] segments = remoteFilePath.split("/");
866         // take last segment as base name
867         String remoteFileName = segments[segments.length - 1];
868         String prefix = getBaseName(remoteFileName);
869         if (prefix.length() < 3) {
870             // prefix must be at least 3 characters long
871             prefix = prefix + "XXX";
872         }
873         String fileExt = getExtension(remoteFileName);
874 
875         // create a unique file name. Add a underscore to prefix so file name is more readable
876         // e.g. myfile_57588758.img rather than myfile57588758.img
877         File tmpFile = FileUtil.createTempFile(prefix + "_", fileExt, parentDir);
878         return tmpFile;
879     }
880 
881     /**
882      * Try to delete a file. Intended for use when cleaning up
883      * in {@code finally} stanzas.
884      *
885      * @param file may be null.
886      */
deleteFile(File file)887     public static void deleteFile(File file) {
888         if (file != null) {
889             file.delete();
890         }
891     }
892 
893     /**
894      * Helper method to build a system-dependent File
895      *
896      * @param parentDir the parent directory to use.
897      * @param pathSegments the relative path segments to use
898      * @return the {@link File} representing given path, with each <var>pathSegment</var>
899      *         separated by {@link File#separatorChar}
900      */
getFileForPath(File parentDir, String... pathSegments)901     public static File getFileForPath(File parentDir, String... pathSegments) {
902         return new File(parentDir, getPath(pathSegments));
903     }
904 
905     /**
906      * Helper method to build a system-dependent relative path
907      *
908      * @param pathSegments the relative path segments to use
909      * @return the {@link String} representing given path, with each <var>pathSegment</var>
910      *         separated by {@link File#separatorChar}
911      */
getPath(String... pathSegments)912     public static String getPath(String... pathSegments) {
913         StringBuilder pathBuilder = new StringBuilder();
914         boolean isFirst = true;
915         for (String path : pathSegments) {
916             if (!isFirst) {
917                 pathBuilder.append(File.separatorChar);
918             } else {
919                 isFirst = false;
920             }
921             pathBuilder.append(path);
922         }
923         return pathBuilder.toString();
924     }
925 
926     /**
927      * Recursively search given directory for first file with given name
928      *
929      * @param dir the directory to search
930      * @param fileName the name of the file to search for
931      * @return the {@link File} or <code>null</code> if it could not be found
932      */
findFile(File dir, String fileName)933     public static File findFile(File dir, String fileName) {
934         if (dir.listFiles() != null) {
935             for (File file : dir.listFiles()) {
936                 if (file.isDirectory()) {
937                     File result = findFile(file, fileName);
938                     if (result != null) {
939                         return result;
940                     }
941                 }
942                 // after exploring the sub-dir, if the dir itself is the only match return it.
943                 if (file.getName().matches(fileName)) {
944                     return file;
945                 }
946             }
947         }
948         return null;
949     }
950 
951     /**
952      * Get all file paths of files in the given directory with name matching the given filter and
953      * also filter the found file by abi arch if abi is not null. Return the first match file found.
954      *
955      * @param fileName {@link String} of the regex to match file path
956      * @param abi {@link IAbi} object of the abi to match the target
957      * @param dirs a varargs array of {@link File} object of the directories to search for files
958      * @return the {@link File} or <code>null</code> if it could not be found
959      */
findFile(String fileName, IAbi abi, File... dirs)960     public static File findFile(String fileName, IAbi abi, File... dirs) throws IOException {
961         for (File dir : dirs) {
962             Set<File> testSrcs = findFilesObject(dir, fileName);
963             if (testSrcs.isEmpty()) {
964                 continue;
965             }
966             Iterator<File> itr = testSrcs.iterator();
967             if (abi == null) {
968                 // Return the first candidate be found.
969                 return itr.next();
970             }
971             while (itr.hasNext()) {
972                 File matchFile = itr.next();
973                 if (matchFile
974                         .getParentFile()
975                         .getName()
976                         .equals(AbiUtils.getArchForAbi(abi.getName()))) {
977                     return matchFile;
978                 }
979             }
980         }
981         // Scan dirs again without abi rule.
982         for (File dir : dirs) {
983             File matchFile = findFile(dir, fileName);
984             if (matchFile != null && matchFile.exists()) {
985                 return matchFile;
986             }
987         }
988         return null;
989     }
990 
991     /**
992      * Recursively find all directories under the given {@code rootDir}
993      *
994      * @param rootDir the root directory to search in
995      * @param relativeParent An optional parent for all {@link File}s returned. If not specified,
996      *            all {@link File}s will be relative to {@code rootDir}.
997      * @return An set of {@link File}s, representing all directories under {@code rootDir},
998      *         including {@code rootDir} itself. If {@code rootDir} is null, an empty set is
999      *         returned.
1000      */
findDirsUnder(File rootDir, File relativeParent)1001     public static Set<File> findDirsUnder(File rootDir, File relativeParent) {
1002         Set<File> dirs = new HashSet<File>();
1003         if (rootDir != null) {
1004             if (!rootDir.isDirectory()) {
1005                 throw new IllegalArgumentException("Can't find dirs under '" + rootDir
1006                         + "'. It's not a directory.");
1007             }
1008             File thisDir = new File(relativeParent, rootDir.getName());
1009             dirs.add(thisDir);
1010             for (File file : rootDir.listFiles()) {
1011                 if (file.isDirectory()) {
1012                     dirs.addAll(findDirsUnder(file, thisDir));
1013                 }
1014             }
1015         }
1016         return dirs;
1017     }
1018 
1019     /**
1020      * Convert the given file size in bytes to a more readable format in X.Y[KMGT] format.
1021      *
1022      * @param sizeLong file size in bytes
1023      * @return descriptive string of file size
1024      */
convertToReadableSize(long sizeLong)1025     public static String convertToReadableSize(long sizeLong) {
1026 
1027         double size = sizeLong;
1028         for (int i = 0; i < SIZE_SPECIFIERS.length; i++) {
1029             if (size < 1024) {
1030                 return String.format("%.1f%c", size, SIZE_SPECIFIERS[i]);
1031             }
1032             size /= 1024f;
1033         }
1034         throw new IllegalArgumentException(
1035                 String.format("Passed a file size of %.2f, I cannot count that high", size));
1036     }
1037 
1038     /**
1039      * The inverse of {@link #convertToReadableSize(long)}. Converts the readable format described
1040      * in {@link #convertToReadableSize(long)} to a byte value.
1041      *
1042      * @param sizeString the string description of the size.
1043      * @return the size in bytes
1044      * @throws IllegalArgumentException if cannot recognize size
1045      */
convertSizeToBytes(String sizeString)1046     public static long convertSizeToBytes(String sizeString) throws IllegalArgumentException {
1047         if (sizeString.isEmpty()) {
1048             throw new IllegalArgumentException("invalid empty string");
1049         }
1050         char sizeSpecifier = sizeString.charAt(sizeString.length() - 1);
1051         long multiplier = findMultiplier(sizeSpecifier);
1052         try {
1053             String numberString = sizeString;
1054             if (multiplier != 1) {
1055                 // strip off last char
1056                 numberString = sizeString.substring(0, sizeString.length() - 1);
1057             }
1058             return multiplier * Long.parseLong(numberString);
1059         } catch (NumberFormatException e) {
1060             throw new IllegalArgumentException(String.format("Unrecognized size %s", sizeString));
1061         }
1062     }
1063 
findMultiplier(char sizeSpecifier)1064     private static long findMultiplier(char sizeSpecifier) {
1065         long multiplier = 1;
1066         for (int i = 1; i < SIZE_SPECIFIERS.length; i++) {
1067             multiplier *= 1024;
1068             if (sizeSpecifier == SIZE_SPECIFIERS[i]) {
1069                 return multiplier;
1070             }
1071         }
1072         // not found
1073         return 1;
1074     }
1075 
1076     /**
1077      * Returns all jar files found in given directory
1078      */
collectJars(File dir)1079     public static List<File> collectJars(File dir) {
1080         List<File> list = new ArrayList<File>();
1081         File[] jarFiles = dir.listFiles(new JarFilter());
1082         if (jarFiles != null) {
1083             list.addAll(Arrays.asList(dir.listFiles(new JarFilter())));
1084         }
1085         return list;
1086     }
1087 
1088     private static class JarFilter implements FilenameFilter {
1089         /**
1090          * {@inheritDoc}
1091          */
1092         @Override
accept(File dir, String name)1093         public boolean accept(File dir, String name) {
1094             return name.endsWith(".jar");
1095         }
1096     }
1097 
1098 
1099     // Backwards-compatibility section
1100     /**
1101      * Utility method to extract entire contents of zip file into given directory
1102      *
1103      * @param zipFile the {@link ZipFile} to extract
1104      * @param destDir the local dir to extract file to
1105      * @throws IOException if failed to extract file
1106      * @deprecated Moved to {@link ZipUtil#extractZip(ZipFile, File)}.
1107      */
1108     @Deprecated
extractZip(ZipFile zipFile, File destDir)1109     public static void extractZip(ZipFile zipFile, File destDir) throws IOException {
1110         ZipUtil.extractZip(zipFile, destDir);
1111     }
1112 
1113     /**
1114      * Utility method to extract one specific file from zip file into a tmp file
1115      *
1116      * @param zipFile  the {@link ZipFile} to extract
1117      * @param filePath the filePath of to extract
1118      * @return the {@link File} or null if not found
1119      * @throws IOException if failed to extract file
1120      * @deprecated Moved to {@link ZipUtil#extractFileFromZip(ZipFile, String)}.
1121      */
1122     @Deprecated
extractFileFromZip(ZipFile zipFile, String filePath)1123     public static File extractFileFromZip(ZipFile zipFile, String filePath) throws IOException {
1124         return ZipUtil.extractFileFromZip(zipFile, filePath);
1125     }
1126 
1127     /**
1128      * Utility method to create a temporary zip file containing the given directory and
1129      * all its contents.
1130      *
1131      * @param dir the directory to zip
1132      * @return a temporary zip {@link File} containing directory contents
1133      * @throws IOException if failed to create zip file
1134      * @deprecated Moved to {@link ZipUtil#createZip(File)}.
1135      */
1136     @Deprecated
createZip(File dir)1137     public static File createZip(File dir) throws IOException {
1138         return ZipUtil.createZip(dir);
1139     }
1140 
1141     /**
1142      * Utility method to create a zip file containing the given directory and
1143      * all its contents.
1144      *
1145      * @param dir the directory to zip
1146      * @param zipFile the zip file to create - it should not already exist
1147      * @throws IOException if failed to create zip file
1148      * @deprecated Moved to {@link ZipUtil#createZip(File, File)}.
1149      */
1150     @Deprecated
createZip(File dir, File zipFile)1151     public static void createZip(File dir, File zipFile) throws IOException {
1152         ZipUtil.createZip(dir, zipFile);
1153     }
1154 
1155     /**
1156      * Close an open {@link ZipFile}, ignoring any exceptions.
1157      *
1158      * @param zipFile the file to close
1159      * @deprecated Moved to {@link ZipUtil#closeZip(ZipFile)}.
1160      */
1161     @Deprecated
closeZip(ZipFile zipFile)1162     public static void closeZip(ZipFile zipFile) {
1163         ZipUtil.closeZip(zipFile);
1164     }
1165 
1166     /**
1167      * Helper method to create a gzipped version of a single file.
1168      *
1169      * @param file     the original file
1170      * @param gzipFile the file to place compressed contents in
1171      * @throws IOException
1172      * @deprecated Moved to {@link ZipUtil#gzipFile(File, File)}.
1173      */
1174     @Deprecated
gzipFile(File file, File gzipFile)1175     public static void gzipFile(File file, File gzipFile) throws IOException {
1176         ZipUtil.gzipFile(file, gzipFile);
1177     }
1178 
1179     /**
1180      * Helper method to calculate CRC-32 for a file.
1181      *
1182      * @param file
1183      * @return CRC-32 of the file
1184      * @throws IOException
1185      */
calculateCrc32(File file)1186     public static long calculateCrc32(File file) throws IOException {
1187         try (BufferedInputStream inputSource = new BufferedInputStream(new FileInputStream(file))) {
1188             return StreamUtil.calculateCrc32(inputSource);
1189         }
1190     }
1191 
1192     /**
1193      * Helper method to calculate md5 for a file.
1194      *
1195      * @param file
1196      * @return md5 of the file
1197      */
calculateMd5(File file)1198     public static String calculateMd5(File file) {
1199         long startTime = System.currentTimeMillis();
1200         try (FileInputStream inputSource = new FileInputStream(file)) {
1201             return StreamUtil.calculateMd5(inputSource);
1202         } catch (IOException e) {
1203             CLog.e(e);
1204         } finally {
1205             InvocationMetricLogger.addInvocationMetrics(
1206                     InvocationMetricKey.MD5_CALCULATION_TIME,
1207                     System.currentTimeMillis() - startTime);
1208             InvocationMetricLogger.addInvocationMetrics(
1209                     InvocationMetricKey.MD5_CALCULATION_COUNT, 1);
1210         }
1211         return "-1";
1212     }
1213 
1214     /**
1215      * Helper method to calculate base64 md5 for a file.
1216      *
1217      * @param file
1218      * @return md5 of the file
1219      */
calculateBase64Md5(File file)1220     public static String calculateBase64Md5(File file) {
1221         try (FileInputStream inputSource = new FileInputStream(file)) {
1222             return StreamUtil.calculateBase64Md5(inputSource);
1223         } catch (IOException e) {
1224             CLog.e(e);
1225         }
1226         return "-1";
1227     }
1228 
1229     /**
1230      * Converts an integer representing unix mode to a set of {@link PosixFilePermission}s
1231      */
unixModeToPosix(int mode)1232     public static Set<PosixFilePermission> unixModeToPosix(int mode) {
1233         Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
1234         for (PosixFilePermission pfp : EnumSet.allOf(PosixFilePermission.class)) {
1235             int m = PERM_MODE_MAP.get(pfp);
1236             if ((m & mode) == m) {
1237                 result.add(pfp);
1238             }
1239         }
1240         return result;
1241     }
1242 
1243     /**
1244      * Get all file paths of files in the given directory with name matching the given filter
1245      *
1246      * @param dir {@link File} object of the directory to search for files recursively
1247      * @param filter {@link String} of the regex to match file names
1248      * @return a set of {@link String} of the file paths
1249      */
findFiles(File dir, String filter)1250     public static Set<String> findFiles(File dir, String filter) throws IOException {
1251         Set<String> files = new HashSet<>();
1252         try (Stream<Path> stream =
1253                 Files.walk(Paths.get(dir.getAbsolutePath()), FileVisitOption.FOLLOW_LINKS)) {
1254             stream.filter(path -> path.getFileName().toString().matches(filter))
1255                     .forEach(path -> files.add(path.toString()));
1256         }
1257         return files;
1258     }
1259 
1260     /**
1261      * Get all files in the given directory with name matching the given filter and also filter the
1262      * found files by abi arch if abi is not null.
1263      *
1264      * @param fileName {@link String} of the regex to match file path
1265      * @param abi {@link IAbi} object of the abi to match the target
1266      * @param includeDirectory whether to include directories in the search result
1267      * @param dirs an array of {@link File} object of the directories to search for files
1268      * @return a set of {@link File}s or empty if it could not be found
1269      */
findFiles( String fileName, IAbi abi, boolean includeDirectory, File... dirs)1270     public static Set<File> findFiles(
1271             String fileName, IAbi abi, boolean includeDirectory, File... dirs) throws IOException {
1272         // files that will be returned at the end
1273         Set<File> abiSpecificFiles = new LinkedHashSet<>();
1274         // files that were found before abi check
1275         Set<File> allFiles = new LinkedHashSet<>();
1276         for (File dir : dirs) {
1277             Set<File> testSrcs = findFilesObject(dir, fileName, includeDirectory);
1278             allFiles.addAll(testSrcs);
1279             if (testSrcs.isEmpty()) {
1280                 continue;
1281             }
1282             Iterator<File> itr = testSrcs.iterator();
1283             if (abi != null) {
1284                 while (itr.hasNext()) {
1285                     File matchFile = itr.next();
1286                     if (matchFile
1287                             .getParentFile()
1288                             .getName()
1289                             .equals(AbiUtils.getArchForAbi(abi.getName()))) {
1290                         abiSpecificFiles.add(matchFile);
1291                     }
1292                 }
1293             }
1294         }
1295         // if arch specific directory structure exists, return files only from the arch specific
1296         // directories
1297         if (!abiSpecificFiles.isEmpty()) {
1298             return abiSpecificFiles;
1299         } else {
1300             // Otherwise, return all files that matched the filename
1301             return allFiles;
1302         }
1303     }
1304 
1305     /**
1306      * Search and return the first directory {@link File} among other directories.
1307      *
1308      * @param dirName The directory name we are looking for.
1309      * @param dirs The list of directories we are searching.
1310      * @return a {@link File} with the directory found or Null if not found.
1311      * @throws IOException
1312      */
findDirectory(String dirName, File... dirs)1313     public static File findDirectory(String dirName, File... dirs) throws IOException {
1314         for (File dir : dirs) {
1315             Set<File> testSrcs = findFilesObject(dir, dirName);
1316             if (testSrcs.isEmpty()) {
1317                 continue;
1318             }
1319             Iterator<File> itr = testSrcs.iterator();
1320             while (itr.hasNext()) {
1321                 File file = itr.next();
1322                 if (file.isDirectory()) {
1323                     return file;
1324                 }
1325             }
1326         }
1327         return null;
1328     }
1329 
1330     /**
1331      * Get all file paths of files in the given directory with name matching the given filter
1332      *
1333      * @param dir {@link File} object of the directory to search for files recursively
1334      * @param filter {@link String} of the regex to match file names
1335      * @return a set of {@link File} of the file objects. @See {@link #findFiles(File, String)}
1336      */
findFilesObject(File dir, String filter)1337     public static Set<File> findFilesObject(File dir, String filter) throws IOException {
1338         return findFilesObject(dir, filter, true);
1339     }
1340 
1341     /**
1342      * Get all file paths of files in the given directory with name matching the given filter
1343      *
1344      * @param dir {@link File} object of the directory to search for files recursively
1345      * @param filter {@link String} of the regex to match file names
1346      * @param includeDirectory whether to include directories in the search result
1347      * @return a set of {@link File} of the file objects. @See {@link #findFiles(File, String)}
1348      */
findFilesObject(File dir, String filter, boolean includeDirectory)1349     public static Set<File> findFilesObject(File dir, String filter, boolean includeDirectory)
1350             throws IOException {
1351         Set<File> files = new LinkedHashSet<>();
1352         try (Stream<Path> stream =
1353                 Files.walk(Paths.get(dir.getAbsolutePath()), FileVisitOption.FOLLOW_LINKS)) {
1354             if (includeDirectory) {
1355                 stream.filter(path -> path.getFileName().toString().matches(filter))
1356                         .forEach(path -> files.add(path.toFile()));
1357             } else {
1358                 stream.filter(
1359                                 path ->
1360                                         path.getFileName().toString().matches(filter)
1361                                                 && path.toFile().isFile())
1362                         .forEach(path -> files.add(path.toFile()));
1363             }
1364         }
1365         return files;
1366     }
1367 
1368     /**
1369      * Get file's content type based it's extension.
1370      * @param filePath the file path
1371      * @return content type
1372      */
getContentType(String filePath)1373     public static String getContentType(String filePath) {
1374         int index = filePath.lastIndexOf('.');
1375         String ext = "";
1376         if (index >= 0) {
1377             ext = filePath.substring(index + 1);
1378         }
1379         LogDataType[] dataTypes = LogDataType.values();
1380         for (LogDataType dataType: dataTypes) {
1381             if (ext.equals(dataType.getFileExt())) {
1382                 return dataType.getContentType();
1383             }
1384         }
1385         return LogDataType.UNKNOWN.getContentType();
1386     }
1387 
1388     /**
1389      * Save a resource file to a directory.
1390      *
1391      * @param resourceStream a {link InputStream} object to the resource to be saved.
1392      * @param destDir a {@link File} object of a directory to where the resource file will be saved.
1393      * @param targetFileName a {@link String} for the name of the file to be saved to.
1394      * @return a {@link File} object of the file saved.
1395      * @throws IOException if the file failed to be saved.
1396      */
saveResourceFile( InputStream resourceStream, File destDir, String targetFileName)1397     public static File saveResourceFile(
1398             InputStream resourceStream, File destDir, String targetFileName) throws IOException {
1399         FileWriter writer = null;
1400         File file = Paths.get(destDir.getAbsolutePath(), targetFileName).toFile();
1401         try {
1402             writer = new FileWriter(file);
1403             StreamUtil.copyStreamToWriter(resourceStream, writer);
1404             return file;
1405         } catch (IOException e) {
1406             CLog.e("IOException while saving resource %s/%s", destDir, targetFileName);
1407             deleteFile(file);
1408             throw e;
1409         } finally {
1410             if (writer != null) {
1411                 writer.close();
1412             }
1413             if (resourceStream != null) {
1414                 resourceStream.close();
1415             }
1416         }
1417     }
1418 
1419     /** Returns the size reported by the directory. */
sizeOfDirectory(File directory)1420     public static Long sizeOfDirectory(File directory) {
1421         if (directory == null || !directory.isDirectory()) {
1422             return null;
1423         }
1424         Path folder = directory.getAbsoluteFile().toPath();
1425         try {
1426             long size = 0;
1427             try (Stream<Path> stream = Files.walk(folder, FileVisitOption.FOLLOW_LINKS)) {
1428                 size =
1429                         stream.filter(p -> p.toFile().isFile())
1430                                 .mapToLong(p -> p.toFile().length())
1431                                 .sum();
1432             }
1433             CLog.d(
1434                     "Directory '%s' has size: %s. Contains: %s",
1435                     directory, size, Arrays.asList(directory.list()));
1436             return size;
1437         } catch (IOException | RuntimeException e) {
1438             CLog.e(e);
1439         }
1440         return null;
1441     }
1442 
1443     /** Returns true if the message is an disk space error. */
isDiskSpaceError(String message)1444     public static boolean isDiskSpaceError(String message) {
1445         return DISK_SPACE_ERRORS.contains(message);
1446     }
1447 
1448     /** Wraps error into a disk space error if needed. */
convertToDiskSpaceIfNeeded(IOException e)1449     public static IOException convertToDiskSpaceIfNeeded(IOException e) {
1450         if (isDiskSpaceError(e.getMessage())) {
1451             return new HarnessIOException(e, InfraErrorIdentifier.NO_DISK_SPACE);
1452         }
1453         return e;
1454     }
1455 }
1456