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