Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2006 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 
     17 package android.os;
     18 
     19 import android.system.ErrnoException;
     20 import android.system.Os;
     21 import android.text.TextUtils;
     22 import android.util.Log;
     23 import android.util.Slog;
     24 
     25 import java.io.BufferedInputStream;
     26 import java.io.ByteArrayOutputStream;
     27 import java.io.File;
     28 import java.io.FileDescriptor;
     29 import java.io.FileInputStream;
     30 import java.io.FileNotFoundException;
     31 import java.io.FileOutputStream;
     32 import java.io.FileWriter;
     33 import java.io.IOException;
     34 import java.io.InputStream;
     35 import java.util.Arrays;
     36 import java.util.Comparator;
     37 import java.util.regex.Pattern;
     38 import java.util.zip.CRC32;
     39 import java.util.zip.CheckedInputStream;
     40 
     41 /**
     42  * Tools for managing files.  Not for public consumption.
     43  * @hide
     44  */
     45 public class FileUtils {
     46     private static final String TAG = "FileUtils";
     47 
     48     public static final int S_IRWXU = 00700;
     49     public static final int S_IRUSR = 00400;
     50     public static final int S_IWUSR = 00200;
     51     public static final int S_IXUSR = 00100;
     52 
     53     public static final int S_IRWXG = 00070;
     54     public static final int S_IRGRP = 00040;
     55     public static final int S_IWGRP = 00020;
     56     public static final int S_IXGRP = 00010;
     57 
     58     public static final int S_IRWXO = 00007;
     59     public static final int S_IROTH = 00004;
     60     public static final int S_IWOTH = 00002;
     61     public static final int S_IXOTH = 00001;
     62 
     63     /** Regular expression for safe filenames: no spaces or metacharacters */
     64     private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
     65 
     66     /**
     67      * Set owner and mode of of given {@link File}.
     68      *
     69      * @param mode to apply through {@code chmod}
     70      * @param uid to apply through {@code chown}, or -1 to leave unchanged
     71      * @param gid to apply through {@code chown}, or -1 to leave unchanged
     72      * @return 0 on success, otherwise errno.
     73      */
     74     public static int setPermissions(File path, int mode, int uid, int gid) {
     75         return setPermissions(path.getAbsolutePath(), mode, uid, gid);
     76     }
     77 
     78     /**
     79      * Set owner and mode of of given path.
     80      *
     81      * @param mode to apply through {@code chmod}
     82      * @param uid to apply through {@code chown}, or -1 to leave unchanged
     83      * @param gid to apply through {@code chown}, or -1 to leave unchanged
     84      * @return 0 on success, otherwise errno.
     85      */
     86     public static int setPermissions(String path, int mode, int uid, int gid) {
     87         try {
     88             Os.chmod(path, mode);
     89         } catch (ErrnoException e) {
     90             Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
     91             return e.errno;
     92         }
     93 
     94         if (uid >= 0 || gid >= 0) {
     95             try {
     96                 Os.chown(path, uid, gid);
     97             } catch (ErrnoException e) {
     98                 Slog.w(TAG, "Failed to chown(" + path + "): " + e);
     99                 return e.errno;
    100             }
    101         }
    102 
    103         return 0;
    104     }
    105 
    106     /**
    107      * Set owner and mode of of given {@link FileDescriptor}.
    108      *
    109      * @param mode to apply through {@code chmod}
    110      * @param uid to apply through {@code chown}, or -1 to leave unchanged
    111      * @param gid to apply through {@code chown}, or -1 to leave unchanged
    112      * @return 0 on success, otherwise errno.
    113      */
    114     public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
    115         try {
    116             Os.fchmod(fd, mode);
    117         } catch (ErrnoException e) {
    118             Slog.w(TAG, "Failed to fchmod(): " + e);
    119             return e.errno;
    120         }
    121 
    122         if (uid >= 0 || gid >= 0) {
    123             try {
    124                 Os.fchown(fd, uid, gid);
    125             } catch (ErrnoException e) {
    126                 Slog.w(TAG, "Failed to fchown(): " + e);
    127                 return e.errno;
    128             }
    129         }
    130 
    131         return 0;
    132     }
    133 
    134     /**
    135      * Return owning UID of given path, otherwise -1.
    136      */
    137     public static int getUid(String path) {
    138         try {
    139             return Os.stat(path).st_uid;
    140         } catch (ErrnoException e) {
    141             return -1;
    142         }
    143     }
    144 
    145     /**
    146      * Perform an fsync on the given FileOutputStream.  The stream at this
    147      * point must be flushed but not yet closed.
    148      */
    149     public static boolean sync(FileOutputStream stream) {
    150         try {
    151             if (stream != null) {
    152                 stream.getFD().sync();
    153             }
    154             return true;
    155         } catch (IOException e) {
    156         }
    157         return false;
    158     }
    159 
    160     // copy a file from srcFile to destFile, return true if succeed, return
    161     // false if fail
    162     public static boolean copyFile(File srcFile, File destFile) {
    163         boolean result = false;
    164         try {
    165             InputStream in = new FileInputStream(srcFile);
    166             try {
    167                 result = copyToFile(in, destFile);
    168             } finally  {
    169                 in.close();
    170             }
    171         } catch (IOException e) {
    172             result = false;
    173         }
    174         return result;
    175     }
    176 
    177     /**
    178      * Copy data from a source stream to destFile.
    179      * Return true if succeed, return false if failed.
    180      */
    181     public static boolean copyToFile(InputStream inputStream, File destFile) {
    182         try {
    183             if (destFile.exists()) {
    184                 destFile.delete();
    185             }
    186             FileOutputStream out = new FileOutputStream(destFile);
    187             try {
    188                 byte[] buffer = new byte[4096];
    189                 int bytesRead;
    190                 while ((bytesRead = inputStream.read(buffer)) >= 0) {
    191                     out.write(buffer, 0, bytesRead);
    192                 }
    193             } finally {
    194                 out.flush();
    195                 try {
    196                     out.getFD().sync();
    197                 } catch (IOException e) {
    198                 }
    199                 out.close();
    200             }
    201             return true;
    202         } catch (IOException e) {
    203             return false;
    204         }
    205     }
    206 
    207     /**
    208      * Check if a filename is "safe" (no metacharacters or spaces).
    209      * @param file  The file to check
    210      */
    211     public static boolean isFilenameSafe(File file) {
    212         // Note, we check whether it matches what's known to be safe,
    213         // rather than what's known to be unsafe.  Non-ASCII, control
    214         // characters, etc. are all unsafe by default.
    215         return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
    216     }
    217 
    218     /**
    219      * Read a text file into a String, optionally limiting the length.
    220      * @param file to read (will not seek, so things like /proc files are OK)
    221      * @param max length (positive for head, negative of tail, 0 for no limit)
    222      * @param ellipsis to add of the file was truncated (can be null)
    223      * @return the contents of the file, possibly truncated
    224      * @throws IOException if something goes wrong reading the file
    225      */
    226     public static String readTextFile(File file, int max, String ellipsis) throws IOException {
    227         InputStream input = new FileInputStream(file);
    228         // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
    229         // input stream, bytes read not equal to buffer size is not necessarily the correct
    230         // indication for EOF; but it is true for BufferedInputStream due to its implementation.
    231         BufferedInputStream bis = new BufferedInputStream(input);
    232         try {
    233             long size = file.length();
    234             if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
    235                 if (size > 0 && (max == 0 || size < max)) max = (int) size;
    236                 byte[] data = new byte[max + 1];
    237                 int length = bis.read(data);
    238                 if (length <= 0) return "";
    239                 if (length <= max) return new String(data, 0, length);
    240                 if (ellipsis == null) return new String(data, 0, max);
    241                 return new String(data, 0, max) + ellipsis;
    242             } else if (max < 0) {  // "tail" mode: keep the last N
    243                 int len;
    244                 boolean rolled = false;
    245                 byte[] last = null;
    246                 byte[] data = null;
    247                 do {
    248                     if (last != null) rolled = true;
    249                     byte[] tmp = last; last = data; data = tmp;
    250                     if (data == null) data = new byte[-max];
    251                     len = bis.read(data);
    252                 } while (len == data.length);
    253 
    254                 if (last == null && len <= 0) return "";
    255                 if (last == null) return new String(data, 0, len);
    256                 if (len > 0) {
    257                     rolled = true;
    258                     System.arraycopy(last, len, last, 0, last.length - len);
    259                     System.arraycopy(data, 0, last, last.length - len, len);
    260                 }
    261                 if (ellipsis == null || !rolled) return new String(last);
    262                 return ellipsis + new String(last);
    263             } else {  // "cat" mode: size unknown, read it all in streaming fashion
    264                 ByteArrayOutputStream contents = new ByteArrayOutputStream();
    265                 int len;
    266                 byte[] data = new byte[1024];
    267                 do {
    268                     len = bis.read(data);
    269                     if (len > 0) contents.write(data, 0, len);
    270                 } while (len == data.length);
    271                 return contents.toString();
    272             }
    273         } finally {
    274             bis.close();
    275             input.close();
    276         }
    277     }
    278 
    279    /**
    280      * Writes string to file. Basically same as "echo -n $string > $filename"
    281      *
    282      * @param filename
    283      * @param string
    284      * @throws IOException
    285      */
    286     public static void stringToFile(String filename, String string) throws IOException {
    287         FileWriter out = new FileWriter(filename);
    288         try {
    289             out.write(string);
    290         } finally {
    291             out.close();
    292         }
    293     }
    294 
    295     /**
    296      * Computes the checksum of a file using the CRC32 checksum routine.
    297      * The value of the checksum is returned.
    298      *
    299      * @param file  the file to checksum, must not be null
    300      * @return the checksum value or an exception is thrown.
    301      */
    302     public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
    303         CRC32 checkSummer = new CRC32();
    304         CheckedInputStream cis = null;
    305 
    306         try {
    307             cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
    308             byte[] buf = new byte[128];
    309             while(cis.read(buf) >= 0) {
    310                 // Just read for checksum to get calculated.
    311             }
    312             return checkSummer.getValue();
    313         } finally {
    314             if (cis != null) {
    315                 try {
    316                     cis.close();
    317                 } catch (IOException e) {
    318                 }
    319             }
    320         }
    321     }
    322 
    323     /**
    324      * Delete older files in a directory until only those matching the given
    325      * constraints remain.
    326      *
    327      * @param minCount Always keep at least this many files.
    328      * @param minAge Always keep files younger than this age.
    329      * @return if any files were deleted.
    330      */
    331     public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
    332         if (minCount < 0 || minAge < 0) {
    333             throw new IllegalArgumentException("Constraints must be positive or 0");
    334         }
    335 
    336         final File[] files = dir.listFiles();
    337         if (files == null) return false;
    338 
    339         // Sort with newest files first
    340         Arrays.sort(files, new Comparator<File>() {
    341             @Override
    342             public int compare(File lhs, File rhs) {
    343                 return (int) (rhs.lastModified() - lhs.lastModified());
    344             }
    345         });
    346 
    347         // Keep at least minCount files
    348         boolean deleted = false;
    349         for (int i = minCount; i < files.length; i++) {
    350             final File file = files[i];
    351 
    352             // Keep files newer than minAge
    353             final long age = System.currentTimeMillis() - file.lastModified();
    354             if (age > minAge) {
    355                 if (file.delete()) {
    356                     Log.d(TAG, "Deleted old file " + file);
    357                     deleted = true;
    358                 }
    359             }
    360         }
    361         return deleted;
    362     }
    363 
    364     /**
    365      * Test if a file lives under the given directory, either as a direct child
    366      * or a distant grandchild.
    367      * <p>
    368      * Both files <em>must</em> have been resolved using
    369      * {@link File#getCanonicalFile()} to avoid symlink or path traversal
    370      * attacks.
    371      */
    372     public static boolean contains(File dir, File file) {
    373         if (file == null) return false;
    374 
    375         String dirPath = dir.getAbsolutePath();
    376         String filePath = file.getAbsolutePath();
    377 
    378         if (dirPath.equals(filePath)) {
    379             return true;
    380         }
    381 
    382         if (!dirPath.endsWith("/")) {
    383             dirPath += "/";
    384         }
    385         return filePath.startsWith(dirPath);
    386     }
    387 
    388     public static boolean deleteContents(File dir) {
    389         File[] files = dir.listFiles();
    390         boolean success = true;
    391         if (files != null) {
    392             for (File file : files) {
    393                 if (file.isDirectory()) {
    394                     success &= deleteContents(file);
    395                 }
    396                 if (!file.delete()) {
    397                     Log.w(TAG, "Failed to delete " + file);
    398                     success = false;
    399                 }
    400             }
    401         }
    402         return success;
    403     }
    404 
    405     private static boolean isValidExtFilenameChar(char c) {
    406         switch (c) {
    407             case '\0':
    408             case '/':
    409                 return false;
    410             default:
    411                 return true;
    412         }
    413     }
    414 
    415     /**
    416      * Check if given filename is valid for an ext4 filesystem.
    417      */
    418     public static boolean isValidExtFilename(String name) {
    419         return (name != null) && name.equals(buildValidExtFilename(name));
    420     }
    421 
    422     /**
    423      * Mutate the given filename to make it valid for an ext4 filesystem,
    424      * replacing any invalid characters with "_".
    425      */
    426     public static String buildValidExtFilename(String name) {
    427         if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
    428             return "(invalid)";
    429         }
    430         final StringBuilder res = new StringBuilder(name.length());
    431         for (int i = 0; i < name.length(); i++) {
    432             final char c = name.charAt(i);
    433             if (isValidExtFilenameChar(c)) {
    434                 res.append(c);
    435             } else {
    436                 res.append('_');
    437             }
    438         }
    439         return res.toString();
    440     }
    441 
    442     private static boolean isValidFatFilenameChar(char c) {
    443         if ((0x00 <= c && c <= 0x1f)) {
    444             return false;
    445         }
    446         switch (c) {
    447             case '"':
    448             case '*':
    449             case '/':
    450             case ':':
    451             case '<':
    452             case '>':
    453             case '?':
    454             case '\\':
    455             case '|':
    456             case 0x7F:
    457                 return false;
    458             default:
    459                 return true;
    460         }
    461     }
    462 
    463     /**
    464      * Check if given filename is valid for a FAT filesystem.
    465      */
    466     public static boolean isValidFatFilename(String name) {
    467         return (name != null) && name.equals(buildValidFatFilename(name));
    468     }
    469 
    470     /**
    471      * Mutate the given filename to make it valid for a FAT filesystem,
    472      * replacing any invalid characters with "_".
    473      */
    474     public static String buildValidFatFilename(String name) {
    475         if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
    476             return "(invalid)";
    477         }
    478         final StringBuilder res = new StringBuilder(name.length());
    479         for (int i = 0; i < name.length(); i++) {
    480             final char c = name.charAt(i);
    481             if (isValidFatFilenameChar(c)) {
    482                 res.append(c);
    483             } else {
    484                 res.append('_');
    485             }
    486         }
    487         return res.toString();
    488     }
    489 
    490     public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
    491         if (path == null) return null;
    492         final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
    493         return (result != null) ? result.getAbsolutePath() : null;
    494     }
    495 
    496     public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
    497         if (paths == null) return null;
    498         final String[] result = new String[paths.length];
    499         for (int i = 0; i < paths.length; i++) {
    500             result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
    501         }
    502         return result;
    503     }
    504 
    505     /**
    506      * Given a path under the "before" directory, rewrite it to live under the
    507      * "after" directory. For example, {@code /before/foo/bar.txt} would become
    508      * {@code /after/foo/bar.txt}.
    509      */
    510     public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
    511         if (file == null) return null;
    512         if (contains(beforeDir, file)) {
    513             final String splice = file.getAbsolutePath().substring(
    514                     beforeDir.getAbsolutePath().length());
    515             return new File(afterDir, splice);
    516         }
    517         return null;
    518     }
    519 }
    520