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