FileUtils.java revision 5aca2b8dc4f4ff2d466a64587d06666c7bbd9749
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.util.Log;
20import android.util.Slog;
21
22import libcore.io.ErrnoException;
23import libcore.io.IoUtils;
24import libcore.io.Libcore;
25import libcore.io.OsConstants;
26
27import java.io.BufferedInputStream;
28import java.io.ByteArrayOutputStream;
29import java.io.File;
30import java.io.FileDescriptor;
31import java.io.FileInputStream;
32import java.io.FileNotFoundException;
33import java.io.FileOutputStream;
34import java.io.FileWriter;
35import java.io.IOException;
36import java.io.InputStream;
37import java.util.Arrays;
38import java.util.Comparator;
39import java.util.regex.Pattern;
40import java.util.zip.CRC32;
41import java.util.zip.CheckedInputStream;
42
43/**
44 * Tools for managing files.  Not for public consumption.
45 * @hide
46 */
47public class FileUtils {
48    private static final String TAG = "FileUtils";
49
50    public static final int S_IRWXU = 00700;
51    public static final int S_IRUSR = 00400;
52    public static final int S_IWUSR = 00200;
53    public static final int S_IXUSR = 00100;
54
55    public static final int S_IRWXG = 00070;
56    public static final int S_IRGRP = 00040;
57    public static final int S_IWGRP = 00020;
58    public static final int S_IXGRP = 00010;
59
60    public static final int S_IRWXO = 00007;
61    public static final int S_IROTH = 00004;
62    public static final int S_IWOTH = 00002;
63    public static final int S_IXOTH = 00001;
64
65    /** Regular expression for safe filenames: no spaces or metacharacters */
66    private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
67
68    /**
69     * Set owner and mode of of given {@link File}.
70     *
71     * @param mode to apply through {@code chmod}
72     * @param uid to apply through {@code chown}, or -1 to leave unchanged
73     * @param gid to apply through {@code chown}, or -1 to leave unchanged
74     * @return 0 on success, otherwise errno.
75     */
76    public static int setPermissions(File path, int mode, int uid, int gid) {
77        return setPermissions(path.getAbsolutePath(), mode, uid, gid);
78    }
79
80    /**
81     * Set owner and mode of of given path.
82     *
83     * @param mode to apply through {@code chmod}
84     * @param uid to apply through {@code chown}, or -1 to leave unchanged
85     * @param gid to apply through {@code chown}, or -1 to leave unchanged
86     * @return 0 on success, otherwise errno.
87     */
88    public static int setPermissions(String path, int mode, int uid, int gid) {
89        try {
90            Libcore.os.chmod(path, mode);
91        } catch (ErrnoException e) {
92            Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
93            return e.errno;
94        }
95
96        if (uid >= 0 || gid >= 0) {
97            try {
98                Libcore.os.chown(path, uid, gid);
99            } catch (ErrnoException e) {
100                Slog.w(TAG, "Failed to chown(" + path + "): " + e);
101                return e.errno;
102            }
103        }
104
105        return 0;
106    }
107
108    /**
109     * Set owner and mode of of given {@link FileDescriptor}.
110     *
111     * @param mode to apply through {@code chmod}
112     * @param uid to apply through {@code chown}, or -1 to leave unchanged
113     * @param gid to apply through {@code chown}, or -1 to leave unchanged
114     * @return 0 on success, otherwise errno.
115     */
116    public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
117        try {
118            Libcore.os.fchmod(fd, mode);
119        } catch (ErrnoException e) {
120            Slog.w(TAG, "Failed to fchmod(): " + e);
121            return e.errno;
122        }
123
124        if (uid >= 0 || gid >= 0) {
125            try {
126                Libcore.os.fchown(fd, uid, gid);
127            } catch (ErrnoException e) {
128                Slog.w(TAG, "Failed to fchown(): " + e);
129                return e.errno;
130            }
131        }
132
133        return 0;
134    }
135
136    /**
137     * Return owning UID of given path, otherwise -1.
138     */
139    public static int getUid(String path) {
140        try {
141            return Libcore.os.stat(path).st_uid;
142        } catch (ErrnoException e) {
143            return -1;
144        }
145    }
146
147    /**
148     * Perform an fsync on the given FileOutputStream.  The stream at this
149     * point must be flushed but not yet closed.
150     */
151    public static boolean sync(FileOutputStream stream) {
152        try {
153            if (stream != null) {
154                stream.getFD().sync();
155            }
156            return true;
157        } catch (IOException e) {
158        }
159        return false;
160    }
161
162    // copy a file from srcFile to destFile, return true if succeed, return
163    // false if fail
164    public static boolean copyFile(File srcFile, File destFile) {
165        boolean result = false;
166        try {
167            InputStream in = new FileInputStream(srcFile);
168            try {
169                result = copyToFile(in, destFile);
170            } finally  {
171                in.close();
172            }
173        } catch (IOException e) {
174            result = false;
175        }
176        return result;
177    }
178
179    /**
180     * Copy data from a source stream to destFile.
181     * Return true if succeed, return false if failed.
182     */
183    public static boolean copyToFile(InputStream inputStream, File destFile) {
184        try {
185            if (destFile.exists()) {
186                destFile.delete();
187            }
188            FileOutputStream out = new FileOutputStream(destFile);
189            try {
190                byte[] buffer = new byte[4096];
191                int bytesRead;
192                while ((bytesRead = inputStream.read(buffer)) >= 0) {
193                    out.write(buffer, 0, bytesRead);
194                }
195            } finally {
196                out.flush();
197                try {
198                    out.getFD().sync();
199                } catch (IOException e) {
200                }
201                out.close();
202            }
203            return true;
204        } catch (IOException e) {
205            return false;
206        }
207    }
208
209    /**
210     * Check if a filename is "safe" (no metacharacters or spaces).
211     * @param file  The file to check
212     */
213    public static boolean isFilenameSafe(File file) {
214        // Note, we check whether it matches what's known to be safe,
215        // rather than what's known to be unsafe.  Non-ASCII, control
216        // characters, etc. are all unsafe by default.
217        return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
218    }
219
220    /**
221     * Read a text file into a String, optionally limiting the length.
222     * @param file to read (will not seek, so things like /proc files are OK)
223     * @param max length (positive for head, negative of tail, 0 for no limit)
224     * @param ellipsis to add of the file was truncated (can be null)
225     * @return the contents of the file, possibly truncated
226     * @throws IOException if something goes wrong reading the file
227     */
228    public static String readTextFile(File file, int max, String ellipsis) throws IOException {
229        InputStream input = new FileInputStream(file);
230        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
231        // input stream, bytes read not equal to buffer size is not necessarily the correct
232        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
233        BufferedInputStream bis = new BufferedInputStream(input);
234        try {
235            long size = file.length();
236            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
237                if (size > 0 && (max == 0 || size < max)) max = (int) size;
238                byte[] data = new byte[max + 1];
239                int length = bis.read(data);
240                if (length <= 0) return "";
241                if (length <= max) return new String(data, 0, length);
242                if (ellipsis == null) return new String(data, 0, max);
243                return new String(data, 0, max) + ellipsis;
244            } else if (max < 0) {  // "tail" mode: keep the last N
245                int len;
246                boolean rolled = false;
247                byte[] last = null;
248                byte[] data = null;
249                do {
250                    if (last != null) rolled = true;
251                    byte[] tmp = last; last = data; data = tmp;
252                    if (data == null) data = new byte[-max];
253                    len = bis.read(data);
254                } while (len == data.length);
255
256                if (last == null && len <= 0) return "";
257                if (last == null) return new String(data, 0, len);
258                if (len > 0) {
259                    rolled = true;
260                    System.arraycopy(last, len, last, 0, last.length - len);
261                    System.arraycopy(data, 0, last, last.length - len, len);
262                }
263                if (ellipsis == null || !rolled) return new String(last);
264                return ellipsis + new String(last);
265            } else {  // "cat" mode: size unknown, read it all in streaming fashion
266                ByteArrayOutputStream contents = new ByteArrayOutputStream();
267                int len;
268                byte[] data = new byte[1024];
269                do {
270                    len = bis.read(data);
271                    if (len > 0) contents.write(data, 0, len);
272                } while (len == data.length);
273                return contents.toString();
274            }
275        } finally {
276            bis.close();
277            input.close();
278        }
279    }
280
281   /**
282     * Writes string to file. Basically same as "echo -n $string > $filename"
283     *
284     * @param filename
285     * @param string
286     * @throws IOException
287     */
288    public static void stringToFile(String filename, String string) throws IOException {
289        FileWriter out = new FileWriter(filename);
290        try {
291            out.write(string);
292        } finally {
293            out.close();
294        }
295    }
296
297    /**
298     * Computes the checksum of a file using the CRC32 checksum routine.
299     * The value of the checksum is returned.
300     *
301     * @param file  the file to checksum, must not be null
302     * @return the checksum value or an exception is thrown.
303     */
304    public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
305        CRC32 checkSummer = new CRC32();
306        CheckedInputStream cis = null;
307
308        try {
309            cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
310            byte[] buf = new byte[128];
311            while(cis.read(buf) >= 0) {
312                // Just read for checksum to get calculated.
313            }
314            return checkSummer.getValue();
315        } finally {
316            if (cis != null) {
317                try {
318                    cis.close();
319                } catch (IOException e) {
320                }
321            }
322        }
323    }
324
325    /**
326     * Delete older files in a directory until only those matching the given
327     * constraints remain.
328     *
329     * @param minCount Always keep at least this many files.
330     * @param minAge Always keep files younger than this age.
331     */
332    public static void 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;
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        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                Log.d(TAG, "Deleting old file " + file);
356                file.delete();
357            }
358        }
359    }
360}
361