blob: dc18dee31f3d0a778cef9ab1b190cd56a1a8988f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Jeff Sharkeyd9526902013-03-14 14:11:57 -070019import android.util.Log;
Jeff Sharkey184a0102013-07-10 16:19:52 -070020import android.util.Slog;
21
22import libcore.io.ErrnoException;
Jeff Sharkey184a0102013-07-10 16:19:52 -070023import libcore.io.Libcore;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070024
Guang Zhu90619812012-10-12 15:50:44 -070025import java.io.BufferedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import java.io.ByteArrayOutputStream;
27import java.io.File;
Jeff Sharkey184a0102013-07-10 16:19:52 -070028import java.io.FileDescriptor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import java.io.FileInputStream;
Wink Saville6d25a992011-06-03 17:03:51 -070030import java.io.FileNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import java.io.FileOutputStream;
Mike Lockwoodda8bb742011-05-28 13:24:04 -040032import java.io.FileWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import java.io.IOException;
34import java.io.InputStream;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070035import java.util.Arrays;
36import java.util.Comparator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import java.util.regex.Pattern;
Wink Saville6d25a992011-06-03 17:03:51 -070038import java.util.zip.CRC32;
39import java.util.zip.CheckedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041/**
42 * Tools for managing files. Not for public consumption.
43 * @hide
44 */
Wink Saville6d25a992011-06-03 17:03:51 -070045public class FileUtils {
Jeff Sharkeyd9526902013-03-14 14:11:57 -070046 private static final String TAG = "FileUtils";
47
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
63 /** Regular expression for safe filenames: no spaces or metacharacters */
64 private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
65
Jeff Sharkey184a0102013-07-10 16:19:52 -070066 /**
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 */
74 public static int setPermissions(File path, int mode, int uid, int gid) {
75 return setPermissions(path.getAbsolutePath(), mode, uid, gid);
76 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077
Jeff Sharkey184a0102013-07-10 16:19:52 -070078 /**
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 */
86 public static int setPermissions(String path, int mode, int uid, int gid) {
87 try {
88 Libcore.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 Libcore.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 */
114 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
115 try {
116 Libcore.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 Libcore.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 */
137 public static int getUid(String path) {
138 try {
139 return Libcore.os.stat(path).st_uid;
140 } catch (ErrnoException e) {
141 return -1;
142 }
143 }
Dianne Hackborn053f61d2013-06-26 18:07:43 -0700144
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700145 /**
146 * Perform an fsync on the given FileOutputStream. The stream at this
147 * point must be flushed but not yet closed.
148 */
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 // copy a file from srcFile to destFile, return true if succeed, return
161 // false if fail
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 }
Guang Zhu90619812012-10-12 15:50:44 -0700176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 /**
178 * Copy data from a source stream to destFile.
179 * Return true if succeed, return false if failed.
180 */
181 public static boolean copyToFile(InputStream inputStream, File destFile) {
182 try {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700183 if (destFile.exists()) {
184 destFile.delete();
185 }
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700186 FileOutputStream out = new FileOutputStream(destFile);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 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 {
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700194 out.flush();
195 try {
196 out.getFD().sync();
197 } catch (IOException e) {
198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 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 */
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 */
226 public static String readTextFile(File file, int max, String ellipsis) throws IOException {
227 InputStream input = new FileInputStream(file);
Guang Zhu90619812012-10-12 15:50:44 -0700228 // 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 try {
Dan Egnor42471dd2010-01-07 17:25:22 -0800233 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 byte[] data = new byte[max + 1];
Guang Zhu90619812012-10-12 15:50:44 -0700237 int length = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 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;
Dan Egnor42471dd2010-01-07 17:25:22 -0800242 } else if (max < 0) { // "tail" mode: keep the last N
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 int len;
244 boolean rolled = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700245 byte[] last = null;
246 byte[] data = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 do {
248 if (last != null) rolled = true;
249 byte[] tmp = last; last = data; data = tmp;
250 if (data == null) data = new byte[-max];
Guang Zhu90619812012-10-12 15:50:44 -0700251 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 } 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);
Dan Egnor42471dd2010-01-07 17:25:22 -0800263 } else { // "cat" mode: size unknown, read it all in streaming fashion
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 ByteArrayOutputStream contents = new ByteArrayOutputStream();
265 int len;
266 byte[] data = new byte[1024];
267 do {
Guang Zhu90619812012-10-12 15:50:44 -0700268 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 if (len > 0) contents.write(data, 0, len);
270 } while (len == data.length);
271 return contents.toString();
272 }
273 } finally {
Guang Zhu90619812012-10-12 15:50:44 -0700274 bis.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 input.close();
276 }
277 }
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400278
279 /**
280 * Writes string to file. Basically same as "echo -n $string > $filename"
281 *
282 * @param filename
283 * @param string
284 * @throws IOException
285 */
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 }
Wink Saville1b9a6a62011-06-04 07:31:35 -0700294
Wink Saville6d25a992011-06-03 17:03:51 -0700295 /**
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 */
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 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700322
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.
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800329 * @return if any files were deleted.
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700330 */
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800331 public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700332 if (minCount < 0 || minAge < 0) {
333 throw new IllegalArgumentException("Constraints must be positive or 0");
334 }
335
336 final File[] files = dir.listFiles();
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800337 if (files == null) return false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700338
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
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800348 boolean deleted = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700349 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) {
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800355 if (file.delete()) {
356 Log.d(TAG, "Deleted old file " + file);
357 deleted = true;
358 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700359 }
360 }
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800361 return deleted;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700362 }
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800363
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 */
372 public static boolean contains(File dir, File file) {
373 String dirPath = dir.getPath();
374 String filePath = file.getPath();
375
376 if (dirPath.equals(filePath)) {
377 return true;
378 }
379
380 if (!dirPath.endsWith("/")) {
381 dirPath += "/";
382 }
383 return filePath.startsWith(dirPath);
384 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385}