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 17 package android.os; 18 19 import android.util.Log; 20 import android.util.Slog; 21 22 import libcore.io.ErrnoException; 23 import libcore.io.IoUtils; 24 import libcore.io.Libcore; 25 import libcore.io.OsConstants; 26 27 import java.io.BufferedInputStream; 28 import java.io.ByteArrayOutputStream; 29 import java.io.File; 30 import java.io.FileDescriptor; 31 import java.io.FileInputStream; 32 import java.io.FileNotFoundException; 33 import java.io.FileOutputStream; 34 import java.io.FileWriter; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.util.Arrays; 38 import java.util.Comparator; 39 import java.util.regex.Pattern; 40 import java.util.zip.CRC32; 41 import java.util.zip.CheckedInputStream; 42 43 /** 44 * Tools for managing files. Not for public consumption. 45 * @hide 46 */ 47 public 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