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