• 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.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      */
setPermissions(File path, int mode, int uid, int gid)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      */
setPermissions(String path, int mode, int uid, int gid)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      */
setPermissions(FileDescriptor fd, int mode, int uid, int gid)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      */
getUid(String path)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      */
sync(FileOutputStream stream)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
copyFile(File srcFile, File destFile)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      */
copyToFile(InputStream inputStream, File destFile)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      */
isFilenameSafe(File file)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      */
readTextFile(File file, int max, String ellipsis)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      */
stringToFile(String filename, String string)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      */
checksumCrc32(File file)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      */
deleteOlderFiles(File dir, int minCount, long minAge)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      */
contains(File dir, File file)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 
deleteContents(File dir)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 
isValidExtFilenameChar(char c)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      */
isValidExtFilename(String name)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      */
buildValidExtFilename(String name)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 
isValidFatFilenameChar(char c)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      */
isValidFatFilename(String name)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      */
buildValidFatFilename(String name)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 
rewriteAfterRename(File beforeDir, File afterDir, String path)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 
rewriteAfterRename(File beforeDir, File afterDir, String[] paths)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      */
rewriteAfterRename(File beforeDir, File afterDir, File file)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