Home | History | Annotate | Download | only in util
      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.ddmlib.Log;
     19 import com.android.tradefed.command.FatalHostError;
     20 import com.android.tradefed.config.Option;
     21 import com.android.tradefed.log.LogUtil.CLog;
     22 import com.android.tradefed.result.LogDataType;
     23 
     24 import java.io.BufferedInputStream;
     25 import java.io.BufferedOutputStream;
     26 import java.io.ByteArrayInputStream;
     27 import java.io.File;
     28 import java.io.FileInputStream;
     29 import java.io.FileNotFoundException;
     30 import java.io.FileOutputStream;
     31 import java.io.FilenameFilter;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.io.OutputStream;
     35 import java.nio.file.Files;
     36 import java.nio.file.Paths;
     37 import java.nio.file.attribute.PosixFilePermission;
     38 import java.util.ArrayList;
     39 import java.util.Arrays;
     40 import java.util.EnumSet;
     41 import java.util.HashMap;
     42 import java.util.HashSet;
     43 import java.util.List;
     44 import java.util.Map;
     45 import java.util.Set;
     46 import java.util.zip.ZipFile;
     47 
     48 /**
     49  * A helper class for file related operations
     50  */
     51 public class FileUtil {
     52 
     53     private static final String LOG_TAG = "FileUtil";
     54     /**
     55      * The minimum allowed disk space in megabytes. File creation methods will throw
     56      * {@link LowDiskSpaceException} if the usable disk space in desired partition is less than
     57      * this amount.
     58      */
     59     @Option(name = "min-disk-space", description = "The minimum allowed disk"
     60         + " space in megabytes for file-creation methods. May be set to"
     61         + " 0 to disable checking.")
     62     private static long mMinDiskSpaceMb = 100;
     63 
     64     private static final char[] SIZE_SPECIFIERS = {
     65             ' ', 'K', 'M', 'G', 'T'
     66     };
     67 
     68     private static String sChmod = "chmod";
     69 
     70     /** A map of {@link PosixFilePermission} to its corresponding Unix file mode */
     71     private static final Map<PosixFilePermission, Integer> PERM_MODE_MAP = new HashMap<>();
     72     static {
     73         PERM_MODE_MAP.put(PosixFilePermission.OWNER_READ,     0b100000000);
     74         PERM_MODE_MAP.put(PosixFilePermission.OWNER_WRITE,    0b010000000);
     75         PERM_MODE_MAP.put(PosixFilePermission.OWNER_EXECUTE,  0b001000000);
     76         PERM_MODE_MAP.put(PosixFilePermission.GROUP_READ,     0b000100000);
     77         PERM_MODE_MAP.put(PosixFilePermission.GROUP_WRITE,    0b000010000);
     78         PERM_MODE_MAP.put(PosixFilePermission.GROUP_EXECUTE,  0b000001000);
     79         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_READ,    0b000000100);
     80         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_WRITE,   0b000000010);
     81         PERM_MODE_MAP.put(PosixFilePermission.OTHERS_EXECUTE, 0b000000001);
     82     }
     83 
     84     public static final int FILESYSTEM_FILENAME_MAX_LENGTH = 255;
     85 
     86     /**
     87      * Exposed for testing. Allows to modify the chmod binary name we look for, in order to tests
     88      * system with no chmod support.
     89      */
     90     protected static void setChmodBinary(String chmodName) {
     91         sChmod = chmodName;
     92     }
     93 
     94     /**
     95      * Thrown if usable disk space is below minimum threshold.
     96      */
     97     @SuppressWarnings("serial")
     98     public static class LowDiskSpaceException extends FatalHostError {
     99 
    100         LowDiskSpaceException(String msg, Throwable cause) {
    101             super(msg, cause);
    102         }
    103 
    104         LowDiskSpaceException(String msg) {
    105             super(msg);
    106         }
    107 
    108     }
    109 
    110     /**
    111      * Method to create a chain of directories, and set them all group execute/read/writable as they
    112      * are created, by calling {@link #chmodGroupRWX(File)}.  Essentially a version of
    113      * {@link File#mkdirs()} that also runs {@link #chmod(File, String)}.
    114      *
    115      * @param file the name of the directory to create, possibly with containing directories that
    116      *        don't yet exist.
    117      * @return {@code true} if {@code file} exists and is a directory, {@code false} otherwise.
    118      */
    119     public static boolean mkdirsRWX(File file) {
    120         File parent = file.getParentFile();
    121 
    122         if (parent != null && !parent.isDirectory()) {
    123             // parent doesn't exist.  recurse upward, which should both mkdir and chmod
    124             if (!mkdirsRWX(parent)) {
    125                 // Couldn't mkdir parent, fail
    126                 Log.w(LOG_TAG, String.format("Failed to mkdir parent dir %s.", parent));
    127                 return false;
    128             }
    129         }
    130 
    131         // by this point the parent exists.  Try to mkdir file
    132         if (file.isDirectory() || file.mkdir()) {
    133             // file should exist.  Try chmod and complain if that fails, but keep going
    134             boolean setPerms = chmodGroupRWX(file);
    135             if (!setPerms) {
    136                 Log.w(LOG_TAG, String.format("Failed to set dir %s to be group accessible.", file));
    137             }
    138         }
    139 
    140         return file.isDirectory();
    141     }
    142 
    143     public static boolean chmodRWXRecursively(File file) {
    144         boolean success = true;
    145         if (!file.setExecutable(true, false)) {
    146             CLog.w("Failed to set %s executable.", file.getAbsolutePath());
    147             success = false;
    148         }
    149         if (!file.setWritable(true, false)) {
    150             CLog.w("Failed to set %s writable.", file.getAbsolutePath());
    151             success = false;
    152         }
    153         if (!file.setReadable(true, false)) {
    154             CLog.w("Failed to set %s readable", file.getAbsolutePath());
    155             success = false;
    156         }
    157 
    158         if (file.isDirectory()) {
    159             File[] children = file.listFiles();
    160             for (File child : children) {
    161                 if (!chmodRWXRecursively(child)) {
    162                     success = false;
    163                 }
    164             }
    165 
    166         }
    167         return success;
    168     }
    169 
    170     public static boolean chmod(File file, String perms) {
    171         Log.d(LOG_TAG, String.format("Attempting to chmod %s to %s",
    172                 file.getAbsolutePath(), perms));
    173         CommandResult result =
    174                 RunUtil.getDefault().runTimedCmd(10 * 1000, sChmod, perms, file.getAbsolutePath());
    175         return result.getStatus().equals(CommandStatus.SUCCESS);
    176     }
    177 
    178     /**
    179      * Performs a best effort attempt to make given file group readable and writable.
    180      * <p/>
    181      * Note that the execute permission is required to make directories accessible.  See
    182      * {@link #chmodGroupRWX(File)}.
    183      * <p/>
    184      * If 'chmod' system command is not supported by underlying OS, will set file to writable by
    185      * all.
    186      *
    187      * @param file the {@link File} to make owner and group writable
    188      * @return <code>true</code> if file was successfully made group writable, <code>false</code>
    189      *         otherwise
    190      */
    191     public static boolean chmodGroupRW(File file) {
    192         if (chmodExists()) {
    193             if (chmod(file, "ug+rw")) {
    194                 return true;
    195             } else {
    196                 Log.d(LOG_TAG, String.format("Failed chmod on %s", file.getAbsolutePath()));
    197                 return false;
    198             }
    199         } else {
    200             Log.d(LOG_TAG, String.format("chmod not available; "
    201                     + "attempting to set %s globally RW", file.getAbsolutePath()));
    202             return file.setWritable(true, false /* false == writable for all */) &&
    203                     file.setReadable(true, false /* false == readable for all */);
    204         }
    205     }
    206 
    207     /**
    208      * Performs a best effort attempt to make given file group executable, readable, and writable.
    209      * <p/>
    210      * If 'chmod' system command is not supported by underlying OS, will attempt to set permissions
    211      * for all users.
    212      *
    213      * @param file the {@link File} to make owner and group writable
    214      * @return <code>true</code> if permissions were set successfully, <code>false</code> otherwise
    215      */
    216     public static boolean chmodGroupRWX(File file) {
    217         if (chmodExists()) {
    218             if (chmod(file, "ug+rwx")) {
    219                 return true;
    220             } else {
    221                 Log.d(LOG_TAG, String.format("Failed chmod on %s", file.getAbsolutePath()));
    222                 return false;
    223             }
    224         } else {
    225             Log.d(LOG_TAG, String.format("chmod not available; "
    226                     + "attempting to set %s globally RWX", file.getAbsolutePath()));
    227             return file.setExecutable(true, false /* false == executable for all */) &&
    228                     file.setWritable(true, false /* false == writable for all */) &&
    229                     file.setReadable(true, false /* false == readable for all */);
    230         }
    231     }
    232 
    233     /**
    234      * Internal helper to determine if 'chmod' is available on the system OS.
    235      */
    236     protected static boolean chmodExists() {
    237         // Silence the scary process exception when chmod is missing, we will log instead.
    238         CommandResult result = RunUtil.getDefault().runTimedCmdSilently(10 * 1000, sChmod);
    239         // We expect a status fail because 'chmod' requires arguments.
    240         if (CommandStatus.FAILED.equals(result.getStatus()) &&
    241                 result.getStderr().contains("chmod: missing operand")) {
    242             return true;
    243         }
    244         CLog.w("Chmod is not supported by this OS.");
    245         return false;
    246     }
    247 
    248     /**
    249      * Recursively set read and exec (if folder) permissions for given file.
    250      */
    251     public static void setReadableRecursive(File file) {
    252         file.setReadable(true);
    253         if (file.isDirectory()) {
    254             file.setExecutable(true);
    255             File[] children = file.listFiles();
    256             if (children != null) {
    257                 for (File childFile : file.listFiles()) {
    258                     setReadableRecursive(childFile);
    259                 }
    260             }
    261         }
    262     }
    263 
    264     /**
    265      * Helper function to create a temp directory in the system default temporary file directory.
    266      *
    267      * @param prefix The prefix string to be used in generating the file's name; must be at least
    268      *            three characters long
    269      * @return the created directory
    270      * @throws IOException if file could not be created
    271      */
    272     public static File createTempDir(String prefix) throws IOException {
    273         return createTempDir(prefix, null);
    274     }
    275 
    276     /**
    277      * Helper function to create a temp directory.
    278      *
    279      * @param prefix The prefix string to be used in generating the file's name; must be at least
    280      *            three characters long
    281      * @param parentDir The parent directory in which the directory is to be created. If
    282      *            <code>null</code> the system default temp directory will be used.
    283      * @return the created directory
    284      * @throws IOException if file could not be created
    285      */
    286     public static File createTempDir(String prefix, File parentDir) throws IOException {
    287         // create a temp file with unique name, then make it a directory
    288         if (parentDir != null) {
    289             CLog.d("Creating temp directory at %s with prefix \"%s\"",
    290               parentDir.getAbsolutePath(), prefix);
    291         }
    292         File tmpDir = File.createTempFile(prefix, "", parentDir);
    293         return deleteFileAndCreateDirWithSameName(tmpDir);
    294     }
    295 
    296     private static File deleteFileAndCreateDirWithSameName(File tmpDir) throws IOException {
    297         tmpDir.delete();
    298         return createDir(tmpDir);
    299     }
    300 
    301     private static File createDir(File tmpDir) throws IOException {
    302         if (!tmpDir.mkdirs()) {
    303             throw new IOException("unable to create directory");
    304         }
    305         return tmpDir;
    306     }
    307 
    308     /**
    309      * Helper function to create a named directory inside your temp folder.
    310      * <p/>
    311      * This directory will not have it's name randomized. If the directory already exists it will
    312      * be returned.
    313      *
    314      * @param name The name of the directory to create in your tmp folder.
    315      * @return the created directory
    316      */
    317     public static File createNamedTempDir(String name) throws IOException {
    318         File namedTmpDir = new File(System.getProperty("java.io.tmpdir"), name);
    319         if (!namedTmpDir.exists()) {
    320             createDir(namedTmpDir);
    321         }
    322         return namedTmpDir;
    323     }
    324 
    325     /**
    326      * Helper wrapper function around {@link File#createTempFile(String, String)} that audits for
    327      * potential out of disk space scenario.
    328      *
    329      * @see File#createTempFile(String, String)
    330      * @throws LowDiskSpaceException if disk space on temporary partition is lower than minimum
    331      *             allowed
    332      */
    333     public static File createTempFile(String prefix, String suffix) throws IOException {
    334         return internalCreateTempFile(prefix, suffix, null);
    335     }
    336 
    337     /**
    338      * Helper wrapper function around {@link File#createTempFile(String, String, File)}
    339      * that audits for potential out of disk space scenario.
    340      *
    341      * @see File#createTempFile(String, String, File)
    342      * @throws LowDiskSpaceException if disk space on partition is lower than minimum allowed
    343      */
    344     public static File createTempFile(String prefix, String suffix, File parentDir)
    345             throws IOException {
    346         return internalCreateTempFile(prefix, suffix, parentDir);
    347     }
    348 
    349     /**
    350      * Internal helper to create a temporary file.
    351      */
    352     private static File internalCreateTempFile(String prefix, String suffix, File parentDir)
    353             throws IOException {
    354         // File.createTempFile add an additional random long in the name so we remove the length.
    355         int overflowLength = prefix.length() + 19 - FILESYSTEM_FILENAME_MAX_LENGTH;
    356         if (suffix != null) {
    357             // suffix may be null
    358             overflowLength += suffix.length();
    359         }
    360         if (overflowLength > 0) {
    361             CLog.w("Filename for prefix: %s and suffix: %s, would be too long for FileSystem,"
    362                     + "truncating it.", prefix, suffix);
    363             // We truncate from suffix in priority because File.createTempFile wants prefix to be
    364             // at least 3 characters.
    365             if (suffix.length() >= overflowLength) {
    366                 int temp = overflowLength;
    367                 overflowLength -= suffix.length();
    368                 suffix = suffix.substring(temp, suffix.length());
    369             } else {
    370                 overflowLength -= suffix.length();
    371                 suffix = "";
    372             }
    373             if (overflowLength > 0) {
    374                 // Whatever remaining to remove after suffix has been truncating should be inside
    375                 // prefix, otherwise there would not be overflow.
    376                 prefix = prefix.substring(0, prefix.length() - overflowLength);
    377             }
    378         }
    379         File returnFile = null;
    380         if (parentDir != null) {
    381             CLog.d("Creating temp file at %s with prefix \"%s\" suffix \"%s\"",
    382                     parentDir.getAbsolutePath(), prefix, suffix);
    383         }
    384         returnFile = File.createTempFile(prefix, suffix, parentDir);
    385         verifyDiskSpace(returnFile);
    386         return returnFile;
    387     }
    388 
    389     /**
    390      * A helper method that hardlinks a file to another file
    391      *
    392      * @param origFile the original file
    393      * @param destFile the destination file
    394      * @throws IOException if failed to hardlink file
    395      */
    396     public static void hardlinkFile(File origFile, File destFile) throws IOException {
    397         // `ln src dest` will create a hardlink (note: not `ln -s src dest`, which creates symlink)
    398         // note that this will fail across filesystem boundaries
    399         // FIXME: should probably just fall back to normal copy if this fails
    400         CommandResult result = linkFile(origFile, destFile, false);
    401         if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
    402             throw new IOException(String.format(
    403                     "Failed to hardlink %s to %s.  Across filesystem boundary?",
    404                     origFile.getAbsolutePath(), destFile.getAbsolutePath()));
    405         }
    406     }
    407 
    408     /**
    409      * A helper method that simlinks a file to another file
    410      *
    411      * @param origFile the original file
    412      * @param destFile the destination file
    413      * @throws IOException if failed to simlink file
    414      */
    415     public static void simlinkFile(File origFile, File destFile) throws IOException {
    416         CommandResult res = linkFile(origFile, destFile, true);
    417         if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
    418             throw new IOException(
    419                     String.format(
    420                             "Error trying to simlink: %s\nstdout:%s\nstderr:%s",
    421                             res.getStatus(), res.getStdout(), res.getStderr()));
    422         }
    423     }
    424 
    425     private static CommandResult linkFile(File origFile, File destFile, boolean simlink)
    426             throws IOException {
    427         if (!origFile.exists()) {
    428             String link = simlink ? "simlink" : "hardlink";
    429             throw new IOException(
    430                     String.format(
    431                             "Cannot %s %s. File does not exist", link, origFile.getAbsolutePath()));
    432         }
    433         List<String> cmd = new ArrayList<>();
    434         cmd.add("ln");
    435         if (simlink) {
    436             cmd.add("-s");
    437         }
    438         cmd.add(origFile.getAbsolutePath());
    439         cmd.add(destFile.getAbsolutePath());
    440         CommandResult result =
    441                 RunUtil.getDefault().runTimedCmdSilently(10 * 1000, cmd.toArray(new String[0]));
    442         return result;
    443     }
    444 
    445     /**
    446      * Recursively hardlink folder contents.
    447      * <p/>
    448      * Only supports copying of files and directories - symlinks are not copied. If the destination
    449      * directory does not exist, it will be created.
    450      *
    451      * @param sourceDir the folder that contains the files to copy
    452      * @param destDir the destination folder
    453      * @throws IOException
    454      */
    455     public static void recursiveHardlink(File sourceDir, File destDir) throws IOException {
    456         if (!destDir.isDirectory() && !destDir.mkdir()) {
    457             throw new IOException(String.format("Could not create directory %s",
    458                     destDir.getAbsolutePath()));
    459         }
    460         for (File childFile : sourceDir.listFiles()) {
    461             File destChild = new File(destDir, childFile.getName());
    462             if (childFile.isDirectory()) {
    463                 recursiveHardlink(childFile, destChild);
    464             } else if (childFile.isFile()) {
    465                 hardlinkFile(childFile, destChild);
    466             }
    467         }
    468     }
    469 
    470     /**
    471      * Recursively simlink folder contents.
    472      *
    473      * <p>Only supports copying of files and directories - symlinks are not copied. If the
    474      * destination directory does not exist, it will be created.
    475      *
    476      * @param sourceDir the folder that contains the files to copy
    477      * @param destDir the destination folder
    478      * @throws IOException
    479      */
    480     public static void recursiveSimlink(File sourceDir, File destDir) throws IOException {
    481         if (!destDir.isDirectory() && !destDir.mkdir()) {
    482             throw new IOException(
    483                     String.format("Could not create directory %s", destDir.getAbsolutePath()));
    484         }
    485         for (File childFile : sourceDir.listFiles()) {
    486             File destChild = new File(destDir, childFile.getName());
    487             if (childFile.isDirectory()) {
    488                 recursiveSimlink(childFile, destChild);
    489             } else if (childFile.isFile()) {
    490                 simlinkFile(childFile, destChild);
    491             }
    492         }
    493     }
    494 
    495     /**
    496      * A helper method that copies a file's contents to a local file
    497      *
    498      * @param origFile the original file to be copied
    499      * @param destFile the destination file
    500      * @throws IOException if failed to copy file
    501      */
    502     public static void copyFile(File origFile, File destFile) throws IOException {
    503         writeToFile(new FileInputStream(origFile), destFile);
    504     }
    505 
    506     /**
    507      * Recursively copy folder contents.
    508      * <p/>
    509      * Only supports copying of files and directories - symlinks are not copied. If the destination
    510      * directory does not exist, it will be created.
    511      *
    512      * @param sourceDir the folder that contains the files to copy
    513      * @param destDir the destination folder
    514      * @throws IOException
    515      */
    516     public static void recursiveCopy(File sourceDir, File destDir) throws IOException {
    517         File[] childFiles = sourceDir.listFiles();
    518         if (childFiles == null) {
    519             throw new IOException(String.format(
    520                     "Failed to recursively copy. Could not determine contents for directory '%s'",
    521                     sourceDir.getAbsolutePath()));
    522         }
    523         if (!destDir.isDirectory() && !destDir.mkdir()) {
    524             throw new IOException(String.format("Could not create directory %s",
    525                 destDir.getAbsolutePath()));
    526         }
    527         for (File childFile : childFiles) {
    528             File destChild = new File(destDir, childFile.getName());
    529             if (childFile.isDirectory()) {
    530                 recursiveCopy(childFile, destChild);
    531             } else if (childFile.isFile()) {
    532                 copyFile(childFile, destChild);
    533             }
    534         }
    535     }
    536 
    537     /**
    538      * A helper method for reading string data from a file
    539      *
    540      * @param sourceFile the file to read from
    541      * @throws IOException
    542      * @throws FileNotFoundException
    543      */
    544     public static String readStringFromFile(File sourceFile) throws IOException {
    545         FileInputStream is = null;
    546         try {
    547             // no need to buffer since StreamUtil does
    548             is = new FileInputStream(sourceFile);
    549             return StreamUtil.getStringFromStream(is);
    550         } finally {
    551             StreamUtil.close(is);
    552         }
    553     }
    554 
    555     /**
    556      * A helper method for writing string data to file
    557      *
    558      * @param inputString the input {@link String}
    559      * @param destFile the destination file to write to
    560      */
    561     public static void writeToFile(String inputString, File destFile) throws IOException {
    562         writeToFile(inputString, destFile, false);
    563     }
    564 
    565     /**
    566      * A helper method for writing or appending string data to file
    567      *
    568      * @param inputString the input {@link String}
    569      * @param destFile the destination file to write or append to
    570      * @param append append to end of file if true, overwrite otherwise
    571      */
    572     public static void writeToFile(String inputString, File destFile, boolean append)
    573             throws IOException {
    574         writeToFile(new ByteArrayInputStream(inputString.getBytes()), destFile, append);
    575     }
    576 
    577     /**
    578      * A helper method for writing stream data to file
    579      *
    580      * @param input the unbuffered input stream
    581      * @param destFile the destination file to write to
    582      */
    583     public static void writeToFile(InputStream input, File destFile) throws IOException {
    584         writeToFile(input, destFile, false);
    585     }
    586 
    587     /**
    588      * A helper method for writing stream data to file
    589      *
    590      * @param input the unbuffered input stream
    591      * @param destFile the destination file to write or append to
    592      * @param append append to end of file if true, overwrite otherwise
    593      */
    594     public static void writeToFile(
    595             InputStream input, File destFile, boolean append) throws IOException {
    596         InputStream origStream = null;
    597         OutputStream destStream = null;
    598         try {
    599             origStream = new BufferedInputStream(input);
    600             destStream = new BufferedOutputStream(new FileOutputStream(destFile, append));
    601             StreamUtil.copyStreams(origStream, destStream);
    602         } finally {
    603             StreamUtil.close(origStream);
    604             StreamUtil.flushAndCloseStream(destStream);
    605         }
    606     }
    607 
    608     private static void verifyDiskSpace(File file) {
    609         // Based on empirical testing File.getUsableSpace is a low cost operation (~ 100 us for
    610         // local disk, ~ 100 ms for network disk). Therefore call it every time tmp file is
    611         // created
    612         long usableSpace = file.getUsableSpace();
    613         long minDiskSpace = mMinDiskSpaceMb * 1024 * 1024;
    614         if (usableSpace < minDiskSpace) {
    615             throw new LowDiskSpaceException(String.format(
    616                     "Available space on %s is %.2f MB. Min is %d MB", file.getAbsolutePath(),
    617                     file.getUsableSpace() / (1024.0 * 1024.0), mMinDiskSpaceMb));
    618         }
    619     }
    620 
    621     /**
    622      * Recursively delete given file or directory and all its contents.
    623      *
    624      * @param rootDir the directory or file to be deleted; can be null
    625      */
    626     public static void recursiveDelete(File rootDir) {
    627         if (rootDir != null) {
    628             if (rootDir.isDirectory()) {
    629                 File[] childFiles = rootDir.listFiles();
    630                 if (childFiles != null) {
    631                     for (File child : childFiles) {
    632                         recursiveDelete(child);
    633                     }
    634                 }
    635             }
    636             rootDir.delete();
    637         }
    638     }
    639 
    640     /**
    641      * Gets the extension for given file name.
    642      *
    643      * @param fileName
    644      * @return the extension or empty String if file has no extension
    645      */
    646     public static String getExtension(String fileName) {
    647         int index = fileName.lastIndexOf('.');
    648         if (index == -1) {
    649             return "";
    650         } else {
    651             return fileName.substring(index);
    652         }
    653     }
    654 
    655     /**
    656      * Gets the base name, without extension, of given file name.
    657      * <p/>
    658      * e.g. getBaseName("file.txt") will return "file"
    659      *
    660      * @param fileName
    661      * @return the base name
    662      */
    663     public static String getBaseName(String fileName) {
    664         int index = fileName.lastIndexOf('.');
    665         if (index == -1) {
    666             return fileName;
    667         } else {
    668             return fileName.substring(0, index);
    669         }
    670     }
    671 
    672     /**
    673      * Utility method to do byte-wise content comparison of two files.
    674      *
    675      * @return <code>true</code> if file contents are identical
    676      */
    677     public static boolean compareFileContents(File file1, File file2) throws IOException {
    678         BufferedInputStream stream1 = null;
    679         BufferedInputStream stream2 = null;
    680 
    681         boolean result = true;
    682         try {
    683             stream1 = new BufferedInputStream(new FileInputStream(file1));
    684             stream2 = new BufferedInputStream(new FileInputStream(file2));
    685             boolean eof = false;
    686             while (!eof) {
    687                 int byte1 = stream1.read();
    688                 int byte2 = stream2.read();
    689                 if (byte1 != byte2) {
    690                     result = false;
    691                     break;
    692                 }
    693                 eof = byte1 == -1;
    694             }
    695         } finally {
    696             StreamUtil.close(stream1);
    697             StreamUtil.close(stream2);
    698         }
    699         return result;
    700     }
    701 
    702     /**
    703      * Helper method which constructs a unique file on temporary disk, whose name corresponds as
    704      * closely as possible to the file name given by the remote file path
    705      *
    706      * @param remoteFilePath the '/' separated remote path to construct the name from
    707      * @param parentDir the parent directory to create the file in. <code>null</code> to use the
    708      * default temporary directory
    709      */
    710     public static File createTempFileForRemote(String remoteFilePath, File parentDir)
    711             throws IOException {
    712         String[] segments = remoteFilePath.split("/");
    713         // take last segment as base name
    714         String remoteFileName = segments[segments.length - 1];
    715         String prefix = getBaseName(remoteFileName);
    716         if (prefix.length() < 3) {
    717             // prefix must be at least 3 characters long
    718             prefix = prefix + "XXX";
    719         }
    720         String fileExt = getExtension(remoteFileName);
    721 
    722         // create a unique file name. Add a underscore to prefix so file name is more readable
    723         // e.g. myfile_57588758.img rather than myfile57588758.img
    724         File tmpFile = FileUtil.createTempFile(prefix + "_", fileExt, parentDir);
    725         return tmpFile;
    726     }
    727 
    728     /**
    729      * Try to delete a file. Intended for use when cleaning up
    730      * in {@code finally} stanzas.
    731      *
    732      * @param file may be null.
    733      */
    734     public static void deleteFile(File file) {
    735         if (file != null) {
    736             file.delete();
    737         }
    738     }
    739 
    740     /**
    741      * Helper method to build a system-dependent File
    742      *
    743      * @param parentDir the parent directory to use.
    744      * @param pathSegments the relative path segments to use
    745      * @return the {@link File} representing given path, with each <var>pathSegment</var>
    746      *         separated by {@link File#separatorChar}
    747      */
    748     public static File getFileForPath(File parentDir, String... pathSegments) {
    749         return new File(parentDir, getPath(pathSegments));
    750     }
    751 
    752     /**
    753      * Helper method to build a system-dependent relative path
    754      *
    755      * @param pathSegments the relative path segments to use
    756      * @return the {@link String} representing given path, with each <var>pathSegment</var>
    757      *         separated by {@link File#separatorChar}
    758      */
    759     public static String getPath(String... pathSegments) {
    760         StringBuilder pathBuilder = new StringBuilder();
    761         boolean isFirst = true;
    762         for (String path : pathSegments) {
    763             if (!isFirst) {
    764                 pathBuilder.append(File.separatorChar);
    765             } else {
    766                 isFirst = false;
    767             }
    768             pathBuilder.append(path);
    769         }
    770         return pathBuilder.toString();
    771     }
    772 
    773     /**
    774      * Recursively search given directory for first file with given name
    775      *
    776      * @param dir the directory to search
    777      * @param fileName the name of the file to search for
    778      * @return the {@link File} or <code>null</code> if it could not be found
    779      */
    780     public static File findFile(File dir, String fileName) {
    781         if (dir.listFiles() != null) {
    782             for (File file : dir.listFiles()) {
    783                 if (file.getName().equals(fileName)) {
    784                     return file;
    785                 } else if (file.isDirectory()) {
    786                     File result = findFile(file, fileName);
    787                     if (result != null) {
    788                         return result;
    789                     }
    790                 }
    791             }
    792         }
    793         return null;
    794     }
    795 
    796     /**
    797      * Recursively find all directories under the given {@code rootDir}
    798      *
    799      * @param rootDir the root directory to search in
    800      * @param relativeParent An optional parent for all {@link File}s returned. If not specified,
    801      *            all {@link File}s will be relative to {@code rootDir}.
    802      * @return An set of {@link File}s, representing all directories under {@code rootDir},
    803      *         including {@code rootDir} itself. If {@code rootDir} is null, an empty set is
    804      *         returned.
    805      */
    806     public static Set<File> findDirsUnder(File rootDir, File relativeParent) {
    807         Set<File> dirs = new HashSet<File>();
    808         if (rootDir != null) {
    809             if (!rootDir.isDirectory()) {
    810                 throw new IllegalArgumentException("Can't find dirs under '" + rootDir
    811                         + "'. It's not a directory.");
    812             }
    813             File thisDir = new File(relativeParent, rootDir.getName());
    814             dirs.add(thisDir);
    815             for (File file : rootDir.listFiles()) {
    816                 if (file.isDirectory()) {
    817                     dirs.addAll(findDirsUnder(file, thisDir));
    818                 }
    819             }
    820         }
    821         return dirs;
    822     }
    823 
    824     /**
    825      * Convert the given file size in bytes to a more readable format in X.Y[KMGT] format.
    826      *
    827      * @param sizeLong file size in bytes
    828      * @return descriptive string of file size
    829      */
    830     public static String convertToReadableSize(long sizeLong) {
    831 
    832         double size = sizeLong;
    833         for (int i = 0; i < SIZE_SPECIFIERS.length; i++) {
    834             if (size < 1024) {
    835                 return String.format("%.1f%c", size, SIZE_SPECIFIERS[i]);
    836             }
    837             size /= 1024f;
    838         }
    839         throw new IllegalArgumentException(
    840                 String.format("Passed a file size of %.2f, I cannot count that high", size));
    841     }
    842 
    843     /**
    844      * The inverse of {@link #convertToReadableSize(long)}. Converts the readable format described
    845      * in {@link #convertToReadableSize(long)} to a byte value.
    846      *
    847      * @param sizeString the string description of the size.
    848      * @return the size in bytes
    849      * @throws IllegalArgumentException if cannot recognize size
    850      */
    851     public static long convertSizeToBytes(String sizeString) throws IllegalArgumentException {
    852         if (sizeString.isEmpty()) {
    853             throw new IllegalArgumentException("invalid empty string");
    854         }
    855         char sizeSpecifier = sizeString.charAt(sizeString.length() - 1);
    856         long multiplier = findMultiplier(sizeSpecifier);
    857         try {
    858             String numberString = sizeString;
    859             if (multiplier != 1) {
    860                 // strip off last char
    861                 numberString = sizeString.substring(0, sizeString.length() - 1);
    862             }
    863             return multiplier * Long.parseLong(numberString);
    864         } catch (NumberFormatException e) {
    865             throw new IllegalArgumentException(String.format("Unrecognized size %s", sizeString));
    866         }
    867     }
    868 
    869     private static long findMultiplier(char sizeSpecifier) {
    870         long multiplier = 1;
    871         for (int i = 1; i < SIZE_SPECIFIERS.length; i++) {
    872             multiplier *= 1024;
    873             if (sizeSpecifier == SIZE_SPECIFIERS[i]) {
    874                 return multiplier;
    875             }
    876         }
    877         // not found
    878         return 1;
    879     }
    880 
    881     /**
    882      * Returns all jar files found in given directory
    883      */
    884     public static List<File> collectJars(File dir) {
    885         List<File> list = new ArrayList<File>();
    886         File[] jarFiles = dir.listFiles(new JarFilter());
    887         if (jarFiles != null) {
    888             list.addAll(Arrays.asList(dir.listFiles(new JarFilter())));
    889         }
    890         return list;
    891     }
    892 
    893     private static class JarFilter implements FilenameFilter {
    894         /**
    895          * {@inheritDoc}
    896          */
    897         @Override
    898         public boolean accept(File dir, String name) {
    899             return name.endsWith(".jar");
    900         }
    901     }
    902 
    903 
    904     // Backwards-compatibility section
    905     /**
    906      * Utility method to extract entire contents of zip file into given directory
    907      *
    908      * @param zipFile the {@link ZipFile} to extract
    909      * @param destDir the local dir to extract file to
    910      * @throws IOException if failed to extract file
    911      * @deprecated Moved to {@link ZipUtil#extractZip(ZipFile, File)}.
    912      */
    913     @Deprecated
    914     public static void extractZip(ZipFile zipFile, File destDir) throws IOException {
    915         ZipUtil.extractZip(zipFile, destDir);
    916     }
    917 
    918     /**
    919      * Utility method to extract one specific file from zip file into a tmp file
    920      *
    921      * @param zipFile  the {@link ZipFile} to extract
    922      * @param filePath the filePath of to extract
    923      * @return the {@link File} or null if not found
    924      * @throws IOException if failed to extract file
    925      * @deprecated Moved to {@link ZipUtil#extractFileFromZip(ZipFile, String)}.
    926      */
    927     @Deprecated
    928     public static File extractFileFromZip(ZipFile zipFile, String filePath) throws IOException {
    929         return ZipUtil.extractFileFromZip(zipFile, filePath);
    930     }
    931 
    932     /**
    933      * Utility method to create a temporary zip file containing the given directory and
    934      * all its contents.
    935      *
    936      * @param dir the directory to zip
    937      * @return a temporary zip {@link File} containing directory contents
    938      * @throws IOException if failed to create zip file
    939      * @deprecated Moved to {@link ZipUtil#createZip(File)}.
    940      */
    941     @Deprecated
    942     public static File createZip(File dir) throws IOException {
    943         return ZipUtil.createZip(dir);
    944     }
    945 
    946     /**
    947      * Utility method to create a zip file containing the given directory and
    948      * all its contents.
    949      *
    950      * @param dir the directory to zip
    951      * @param zipFile the zip file to create - it should not already exist
    952      * @throws IOException if failed to create zip file
    953      * @deprecated Moved to {@link ZipUtil#createZip(File, File)}.
    954      */
    955     @Deprecated
    956     public static void createZip(File dir, File zipFile) throws IOException {
    957         ZipUtil.createZip(dir, zipFile);
    958     }
    959 
    960     /**
    961      * Close an open {@link ZipFile}, ignoring any exceptions.
    962      *
    963      * @param zipFile the file to close
    964      * @deprecated Moved to {@link ZipUtil#closeZip(ZipFile)}.
    965      */
    966     @Deprecated
    967     public static void closeZip(ZipFile zipFile) {
    968         ZipUtil.closeZip(zipFile);
    969     }
    970 
    971     /**
    972      * Helper method to create a gzipped version of a single file.
    973      *
    974      * @param file     the original file
    975      * @param gzipFile the file to place compressed contents in
    976      * @throws IOException
    977      * @deprecated Moved to {@link ZipUtil#gzipFile(File, File)}.
    978      */
    979     @Deprecated
    980     public static void gzipFile(File file, File gzipFile) throws IOException {
    981         ZipUtil.gzipFile(file, gzipFile);
    982     }
    983 
    984     /**
    985      * Helper method to calculate md5 for a file.
    986      *
    987      * @param file
    988      * @return md5 of the file
    989      * @throws IOException
    990      */
    991     public static String calculateMd5(File file) throws IOException {
    992         FileInputStream inputSource = new FileInputStream(file);
    993         return StreamUtil.calculateMd5(inputSource);
    994     }
    995 
    996     /**
    997      * Converts an integer representing unix mode to a set of {@link PosixFilePermission}s
    998      */
    999     public static Set<PosixFilePermission> unixModeToPosix(int mode) {
   1000         Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
   1001         for (PosixFilePermission pfp : EnumSet.allOf(PosixFilePermission.class)) {
   1002             int m = PERM_MODE_MAP.get(pfp);
   1003             if ((m & mode) == m) {
   1004                 result.add(pfp);
   1005             }
   1006         }
   1007         return result;
   1008     }
   1009 
   1010     /**
   1011      * Get all file paths of files in the given directory with name matching the given filter
   1012      *
   1013      * @param dir {@link File} object of the directory to search for files recursively
   1014      * @param filter {@link String} of the regex to match file names
   1015      * @return a set of {@link String} of the file paths
   1016      */
   1017     public static Set<String> findFiles(File dir, String filter) throws IOException {
   1018         Set<String> files = new HashSet<String>();
   1019         Files.walk(Paths.get(dir.getAbsolutePath()))
   1020                 .filter(path -> new File(path.toString()).getName().matches(filter))
   1021                 .forEach(path -> files.add(path.toString()));
   1022         return files;
   1023     }
   1024 
   1025     /**
   1026      * Get file's content type based it's extension.
   1027      * @param filePath the file path
   1028      * @return content type
   1029      */
   1030     public static String getContentType(String filePath) {
   1031         int index = filePath.lastIndexOf('.');
   1032         String ext = "";
   1033         if (index >= 0) {
   1034             ext = filePath.substring(index + 1);
   1035         }
   1036         LogDataType[] dataTypes = LogDataType.values();
   1037         for (LogDataType dataType: dataTypes) {
   1038             if (ext.equals(dataType.getFileExt())) {
   1039                 return dataType.getContentType();
   1040             }
   1041         }
   1042         return LogDataType.UNKNOWN.getContentType();
   1043     }
   1044 }
   1045