• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
setPermissions(File path, int mode, int uid, int gid)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      */
setPermissions(String path, int mode, int uid, int gid)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      */
setPermissions(FileDescriptor fd, int mode, int uid, int gid)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      */
getUid(String path)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      */
sync(FileOutputStream stream)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
copyFile(File srcFile, File destFile)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      */
copyToFile(InputStream inputStream, File destFile)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      */
isFilenameSafe(File file)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      */
readTextFile(File file, int max, String ellipsis)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      */
stringToFile(String filename, String string)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      */
checksumCrc32(File file)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      */
deleteOlderFiles(File dir, int minCount, long minAge)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      */
contains(File dir, File file)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 
deleteContents(File dir)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      */
isValidExtFilename(String name)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 
rewriteAfterRename(File beforeDir, File afterDir, String path)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 
rewriteAfterRename(File beforeDir, File afterDir, String[] paths)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      */
rewriteAfterRename(File beforeDir, File afterDir, File file)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