FileUtils.java revision 3a44f3f1b446315ef894e01d2ab9b5388c2bd8c4
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
17package android.os;
18
19import android.system.ErrnoException;
20import android.text.TextUtils;
21import android.system.Os;
22import android.system.OsConstants;
23import android.util.Log;
24import android.util.Slog;
25
26import java.io.BufferedInputStream;
27import java.io.ByteArrayOutputStream;
28import java.io.File;
29import java.io.FileDescriptor;
30import java.io.FileInputStream;
31import java.io.FileNotFoundException;
32import java.io.FileOutputStream;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.io.InputStream;
36import java.util.Arrays;
37import java.util.Comparator;
38import java.util.regex.Pattern;
39import java.util.zip.CRC32;
40import java.util.zip.CheckedInputStream;
41
42/**
43 * Tools for managing files.  Not for public consumption.
44 * @hide
45 */
46public 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        String dirPath = dir.getAbsolutePath();
375        String filePath = file.getAbsolutePath();
376
377        if (dirPath.equals(filePath)) {
378            return true;
379        }
380
381        if (!dirPath.endsWith("/")) {
382            dirPath += "/";
383        }
384        return filePath.startsWith(dirPath);
385    }
386
387    public static void deleteContents(File dir) {
388        File[] files = dir.listFiles();
389        if (files != null) {
390            for (File file : files) {
391                if (file.isDirectory()) {
392                    deleteContents(file);
393                }
394                file.delete();
395            }
396        }
397    }
398
399    /**
400     * Assert that given filename is valid on ext4.
401     */
402    public static boolean isValidExtFilename(String name) {
403        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
404            return false;
405        }
406        for (int i = 0; i < name.length(); i++) {
407            final char c = name.charAt(i);
408            if (c == '\0' || c == '/') {
409                return false;
410            }
411        }
412        return true;
413    }
414}
415