1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
|
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.system.ErrnoException;
import android.text.TextUtils;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Slog;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
/**
* Tools for managing files. Not for public consumption.
* @hide
*/
public class FileUtils {
private static final String TAG = "FileUtils";
public static final int S_IRWXU = 00700;
public static final int S_IRUSR = 00400;
public static final int S_IWUSR = 00200;
public static final int S_IXUSR = 00100;
public static final int S_IRWXG = 00070;
public static final int S_IRGRP = 00040;
public static final int S_IWGRP = 00020;
public static final int S_IXGRP = 00010;
public static final int S_IRWXO = 00007;
public static final int S_IROTH = 00004;
public static final int S_IWOTH = 00002;
public static final int S_IXOTH = 00001;
/** Regular expression for safe filenames: no spaces or metacharacters */
private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
/**
* Set owner and mode of of given {@link File}.
*
* @param mode to apply through {@code chmod}
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
*/
public static int setPermissions(File path, int mode, int uid, int gid) {
return setPermissions(path.getAbsolutePath(), mode, uid, gid);
}
/**
* Set owner and mode of of given path.
*
* @param mode to apply through {@code chmod}
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
*/
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
Os.chmod(path, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
return e.errno;
}
if (uid >= 0 || gid >= 0) {
try {
Os.chown(path, uid, gid);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chown(" + path + "): " + e);
return e.errno;
}
}
return 0;
}
/**
* Set owner and mode of of given {@link FileDescriptor}.
*
* @param mode to apply through {@code chmod}
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
*/
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
try {
Os.fchmod(fd, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fchmod(): " + e);
return e.errno;
}
if (uid >= 0 || gid >= 0) {
try {
Os.fchown(fd, uid, gid);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fchown(): " + e);
return e.errno;
}
}
return 0;
}
/**
* Return owning UID of given path, otherwise -1.
*/
public static int getUid(String path) {
try {
return Os.stat(path).st_uid;
} catch (ErrnoException e) {
return -1;
}
}
/**
* Perform an fsync on the given FileOutputStream. The stream at this
* point must be flushed but not yet closed.
*/
public static boolean sync(FileOutputStream stream) {
try {
if (stream != null) {
stream.getFD().sync();
}
return true;
} catch (IOException e) {
}
return false;
}
// copy a file from srcFile to destFile, return true if succeed, return
// false if fail
public static boolean copyFile(File srcFile, File destFile) {
boolean result = false;
try {
InputStream in = new FileInputStream(srcFile);
try {
result = copyToFile(in, destFile);
} finally {
in.close();
}
} catch (IOException e) {
result = false;
}
return result;
}
/**
* Copy data from a source stream to destFile.
* Return true if succeed, return false if failed.
*/
public static boolean copyToFile(InputStream inputStream, File destFile) {
try {
if (destFile.exists()) {
destFile.delete();
}
FileOutputStream out = new FileOutputStream(destFile);
try {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) >= 0) {
out.write(buffer, 0, bytesRead);
}
} finally {
out.flush();
try {
out.getFD().sync();
} catch (IOException e) {
}
out.close();
}
return true;
} catch (IOException e) {
return false;
}
}
/**
* Check if a filename is "safe" (no metacharacters or spaces).
* @param file The file to check
*/
public static boolean isFilenameSafe(File file) {
// Note, we check whether it matches what's known to be safe,
// rather than what's known to be unsafe. Non-ASCII, control
// characters, etc. are all unsafe by default.
return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
}
/**
* Read a text file into a String, optionally limiting the length.
* @param file to read (will not seek, so things like /proc files are OK)
* @param max length (positive for head, negative of tail, 0 for no limit)
* @param ellipsis to add of the file was truncated (can be null)
* @return the contents of the file, possibly truncated
* @throws IOException if something goes wrong reading the file
*/
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
InputStream input = new FileInputStream(file);
// wrapping a BufferedInputStream around it because when reading /proc with unbuffered
// input stream, bytes read not equal to buffer size is not necessarily the correct
// indication for EOF; but it is true for BufferedInputStream due to its implementation.
BufferedInputStream bis = new BufferedInputStream(input);
try {
long size = file.length();
if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
if (size > 0 && (max == 0 || size < max)) max = (int) size;
byte[] data = new byte[max + 1];
int length = bis.read(data);
if (length <= 0) return "";
if (length <= max) return new String(data, 0, length);
if (ellipsis == null) return new String(data, 0, max);
return new String(data, 0, max) + ellipsis;
} else if (max < 0) { // "tail" mode: keep the last N
int len;
boolean rolled = false;
byte[] last = null;
byte[] data = null;
do {
if (last != null) rolled = true;
byte[] tmp = last; last = data; data = tmp;
if (data == null) data = new byte[-max];
len = bis.read(data);
} while (len == data.length);
if (last == null && len <= 0) return "";
if (last == null) return new String(data, 0, len);
if (len > 0) {
rolled = true;
System.arraycopy(last, len, last, 0, last.length - len);
System.arraycopy(data, 0, last, last.length - len, len);
}
if (ellipsis == null || !rolled) return new String(last);
return ellipsis + new String(last);
} else { // "cat" mode: size unknown, read it all in streaming fashion
ByteArrayOutputStream contents = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
do {
len = bis.read(data);
if (len > 0) contents.write(data, 0, len);
} while (len == data.length);
return contents.toString();
}
} finally {
bis.close();
input.close();
}
}
/**
* Writes string to file. Basically same as "echo -n $string > $filename"
*
* @param filename
* @param string
* @throws IOException
*/
public static void stringToFile(String filename, String string) throws IOException {
FileWriter out = new FileWriter(filename);
try {
out.write(string);
} finally {
out.close();
}
}
/**
* Computes the checksum of a file using the CRC32 checksum routine.
* The value of the checksum is returned.
*
* @param file the file to checksum, must not be null
* @return the checksum value or an exception is thrown.
*/
public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
CRC32 checkSummer = new CRC32();
CheckedInputStream cis = null;
try {
cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
byte[] buf = new byte[128];
while(cis.read(buf) >= 0) {
// Just read for checksum to get calculated.
}
return checkSummer.getValue();
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException e) {
}
}
}
}
/**
* Delete older files in a directory until only those matching the given
* constraints remain.
*
* @param minCount Always keep at least this many files.
* @param minAge Always keep files younger than this age.
* @return if any files were deleted.
*/
public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
if (minCount < 0 || minAge < 0) {
throw new IllegalArgumentException("Constraints must be positive or 0");
}
final File[] files = dir.listFiles();
if (files == null) return false;
// Sort with newest files first
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
return (int) (rhs.lastModified() - lhs.lastModified());
}
});
// Keep at least minCount files
boolean deleted = false;
for (int i = minCount; i < files.length; i++) {
final File file = files[i];
// Keep files newer than minAge
final long age = System.currentTimeMillis() - file.lastModified();
if (age > minAge) {
if (file.delete()) {
Log.d(TAG, "Deleted old file " + file);
deleted = true;
}
}
}
return deleted;
}
/**
* Test if a file lives under the given directory, either as a direct child
* or a distant grandchild.
* <p>
* Both files <em>must</em> have been resolved using
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
*/
public static boolean contains(File dir, File file) {
String dirPath = dir.getAbsolutePath();
String filePath = file.getAbsolutePath();
if (dirPath.equals(filePath)) {
return true;
}
if (!dirPath.endsWith("/")) {
dirPath += "/";
}
return filePath.startsWith(dirPath);
}
public static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
success &= deleteContents(file);
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete " + file);
success = false;
}
}
}
return success;
}
/**
* Assert that given filename is valid on ext4.
*/
public static boolean isValidExtFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
return false;
}
for (int i = 0; i < name.length(); i++) {
final char c = name.charAt(i);
if (c == '\0' || c == '/') {
return false;
}
}
return true;
}
public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
return (result != null) ? result.getAbsolutePath() : null;
}
/**
* Given a path under the "before" directory, rewrite it to live under the
* "after" directory. For example, {@code /before/foo/bar.txt} would become
* {@code /after/foo/bar.txt}.
*/
public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
if (contains(beforeDir, file)) {
final String splice = file.getAbsolutePath().substring(
beforeDir.getAbsolutePath().length());
return new File(afterDir, splice);
}
return null;
}
}
|