OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.crash; |
| 6 |
| 7 import android.util.Log; |
| 8 |
| 9 import org.chromium.base.VisibleForTesting; |
| 10 |
| 11 import java.io.File; |
| 12 import java.io.FilenameFilter; |
| 13 import java.util.Arrays; |
| 14 import java.util.Comparator; |
| 15 import java.util.regex.Matcher; |
| 16 import java.util.regex.Pattern; |
| 17 |
| 18 /** |
| 19 * Responsible for the Crash Report directory. It routinely scans the directory |
| 20 * for new Minidump files and takes appropriate actions by either uploading new |
| 21 * crash dumps or deleting old ones. |
| 22 */ |
| 23 public class CrashFileManager { |
| 24 private static final String TAG = "CrashFileManager"; |
| 25 |
| 26 @VisibleForTesting |
| 27 static final String CRASH_DUMP_DIR = "Crash Reports"; |
| 28 |
| 29 // This should mirror the C++ CrashUploadList::kReporterLogFilename variable
. |
| 30 @VisibleForTesting |
| 31 static final String CRASH_DUMP_LOGFILE = CRASH_DUMP_DIR + "/uploads.log"; |
| 32 |
| 33 private static final Pattern MINIDUMP_PATTERN = |
| 34 Pattern.compile("\\.dmp([0-9]*)(.try[0-9])?\\z"); |
| 35 |
| 36 private static final Pattern UPLOADED_MINIDUMP_PATTERN = Pattern.compile("\\
.up([0-9]*)\\z"); |
| 37 |
| 38 private static final String UPLOADED_MINIDUMP_SUFFIX = ".up"; |
| 39 |
| 40 private static final String UPLOAD_ATTEMPT_DELIMITER = ".try"; |
| 41 |
| 42 @VisibleForTesting |
| 43 protected static final String TMP_SUFFIX = ".tmp"; |
| 44 |
| 45 private static final Pattern TMP_PATTERN = Pattern.compile("\\.tmp\\z"); |
| 46 |
| 47 private static Comparator<File> sFileComparator = new Comparator<File>() { |
| 48 @Override |
| 49 public int compare(File lhs, File rhs) { |
| 50 if (lhs == rhs) { |
| 51 return 0; |
| 52 } else if (lhs.lastModified() < rhs.lastModified()) { |
| 53 return -1; |
| 54 } else { |
| 55 return 1; |
| 56 } |
| 57 } |
| 58 }; |
| 59 |
| 60 @VisibleForTesting |
| 61 static boolean deleteFile(File fileToDelete) { |
| 62 boolean isSuccess = fileToDelete.delete(); |
| 63 if (!isSuccess) { |
| 64 Log.w(TAG, "Unable to delete " + fileToDelete.getAbsolutePath()); |
| 65 } |
| 66 return isSuccess; |
| 67 } |
| 68 |
| 69 public static String tryIncrementAttemptNumber(File mFileToUpload) { |
| 70 String newName = incrementAttemptNumber(mFileToUpload.getPath()); |
| 71 return mFileToUpload.renameTo(new File(newName)) ? newName : null; |
| 72 } |
| 73 |
| 74 /** |
| 75 * @return The file name to rename to after an addition attempt to upload |
| 76 */ |
| 77 @VisibleForTesting |
| 78 public static String incrementAttemptNumber(String filename) { |
| 79 int numTried = readAttemptNumber(filename); |
| 80 if (numTried > 0) { |
| 81 int newCount = numTried + 1; |
| 82 return filename.replaceAll(UPLOAD_ATTEMPT_DELIMITER + numTried, |
| 83 UPLOAD_ATTEMPT_DELIMITER + newCount); |
| 84 } else { |
| 85 return filename + UPLOAD_ATTEMPT_DELIMITER + "1"; |
| 86 } |
| 87 } |
| 88 |
| 89 @VisibleForTesting |
| 90 public static int readAttemptNumber(String filename) { |
| 91 int tryIndex = filename.lastIndexOf(UPLOAD_ATTEMPT_DELIMITER); |
| 92 if (tryIndex >= 0) { |
| 93 tryIndex += UPLOAD_ATTEMPT_DELIMITER.length(); |
| 94 // To avoid out of bound exceptions |
| 95 if (tryIndex < filename.length()) { |
| 96 // We don't try more than 3 times. |
| 97 String numTriesString = filename.substring( |
| 98 tryIndex, tryIndex + 1); |
| 99 try { |
| 100 return Integer.parseInt(numTriesString); |
| 101 } catch (NumberFormatException ignored) { |
| 102 return 0; |
| 103 } |
| 104 } |
| 105 } |
| 106 return 0; |
| 107 } |
| 108 |
| 109 public static boolean tryMarkAsUploaded(File mFileToUpload) { |
| 110 return mFileToUpload.renameTo( |
| 111 new File(mFileToUpload.getPath().replaceAll( |
| 112 "\\.dmp", UPLOADED_MINIDUMP_SUFFIX))); |
| 113 } |
| 114 |
| 115 private final File mCacheDir; |
| 116 |
| 117 public CrashFileManager(File cacheDir) { |
| 118 if (cacheDir == null) { |
| 119 throw new NullPointerException("Specified context cannot be null."); |
| 120 } else if (!cacheDir.isDirectory()) { |
| 121 throw new IllegalArgumentException(cacheDir.getAbsolutePath() |
| 122 + " is not a directory."); |
| 123 } |
| 124 mCacheDir = cacheDir; |
| 125 } |
| 126 |
| 127 public File[] getAllMinidumpFiles() { |
| 128 return getMatchingFiles(MINIDUMP_PATTERN); |
| 129 } |
| 130 |
| 131 public File[] getAllMinidumpFilesSorted() { |
| 132 File[] minidumps = getAllMinidumpFiles(); |
| 133 Arrays.sort(minidumps, sFileComparator); |
| 134 return minidumps; |
| 135 } |
| 136 |
| 137 public void cleanOutAllNonFreshMinidumpFiles() { |
| 138 for (File f : getAllUploadedFiles()) { |
| 139 deleteFile(f); |
| 140 } |
| 141 for (File f : getAllTempFiles()) { |
| 142 deleteFile(f); |
| 143 } |
| 144 } |
| 145 |
| 146 /** |
| 147 * Deletes all files including unsent crash reports. |
| 148 * Note: This method is called from multiple threads, but it is not thread-s
afe. It will |
| 149 * generate warning messages in logs if race condition occurs. |
| 150 */ |
| 151 @VisibleForTesting |
| 152 public void cleanAllMiniDumps() { |
| 153 cleanOutAllNonFreshMinidumpFiles(); |
| 154 |
| 155 for (File f : getAllMinidumpFiles()) { |
| 156 deleteFile(f); |
| 157 } |
| 158 } |
| 159 |
| 160 @VisibleForTesting |
| 161 File[] getMatchingFiles(final Pattern pattern) { |
| 162 // Get dump dir and get all files with specified suffix.. The path |
| 163 // constructed here must match chrome_paths.cc (see case |
| 164 // chrome::DIR_CRASH_DUMPS). |
| 165 File crashDir = getCrashDirectory(); |
| 166 if (!crashDir.exists()) { |
| 167 Log.w(TAG, crashDir.getAbsolutePath() + " does not exist!"); |
| 168 return new File[] {}; |
| 169 } |
| 170 if (!crashDir.isDirectory()) { |
| 171 Log.w(TAG, crashDir.getAbsolutePath() + " is not a directory!"); |
| 172 return new File[] {}; |
| 173 } |
| 174 File[] minidumps = crashDir.listFiles(new FilenameFilter() { |
| 175 @Override |
| 176 public boolean accept(File dir, String filename) { |
| 177 Matcher match = pattern.matcher(filename); |
| 178 int tries = readAttemptNumber(filename); |
| 179 return match.find() && tries < MinidumpUploadService.MAX_TRIES_A
LLOWED; |
| 180 } |
| 181 }); |
| 182 return minidumps; |
| 183 } |
| 184 |
| 185 @VisibleForTesting |
| 186 File[] getAllUploadedFiles() { |
| 187 return getMatchingFiles(UPLOADED_MINIDUMP_PATTERN); |
| 188 } |
| 189 |
| 190 @VisibleForTesting |
| 191 File getCrashDirectory() { |
| 192 return new File(mCacheDir, CRASH_DUMP_DIR); |
| 193 } |
| 194 |
| 195 File getCrashUploadLogFile() { |
| 196 return new File(mCacheDir, CRASH_DUMP_LOGFILE); |
| 197 } |
| 198 |
| 199 private File[] getAllTempFiles() { |
| 200 return getMatchingFiles(TMP_PATTERN); |
| 201 } |
| 202 } |
OLD | NEW |