blob: ff3e27785b74ffd72f6fd07c5cbe09344e5d5cc4 [file] [log] [blame]
/*
* 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.util.Log;
import android.util.Slog;
import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.OsConstants;
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 {
Libcore.os.chmod(path, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
return e.errno;
}
if (uid >= 0 || gid >= 0) {
try {
Libcore.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 {
Libcore.os.fchmod(fd, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to fchmod(): " + e);
return e.errno;
}
if (uid >= 0 || gid >= 0) {
try {
Libcore.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 Libcore.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.
*/
public static void 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;
// 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
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) {
Log.d(TAG, "Deleting old file " + file);
file.delete();
}
}
}
}