Skip to content

Commit 70124f4

Browse files
authored
Update Crashlytics file system to support multi-process apps (#3715)
* Update Crashlytics file system to support multi-process apps more elegantly * Fix nit in test case
1 parent 793415f commit 70124f4

File tree

3 files changed

+59
-27
lines changed

3 files changed

+59
-27
lines changed

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/FileStoreTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414

1515
package com.google.firebase.crashlytics.internal.persistence;
1616

17+
import static com.google.firebase.crashlytics.internal.persistence.FileStore.sanitizeName;
18+
1719
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
1820
import java.io.File;
1921
import java.util.Arrays;
2022
import java.util.List;
2123

24+
@SuppressWarnings("ResultOfMethodCallIgnored") // Convenient use of files.
2225
public class FileStoreTest extends CrashlyticsTestCase {
2326
FileStore fileStore;
2427

@@ -31,6 +34,7 @@ protected void setUp() throws Exception {
3134
public void testGetCommonFile() {
3235
File commonFile = fileStore.getCommonFile("testCommonFile");
3336
assertFalse(commonFile.exists());
37+
assertNotNull(commonFile.getParentFile());
3438
assertTrue(commonFile.getParentFile().exists());
3539
}
3640

@@ -39,6 +43,7 @@ public void testGetSessionFile() {
3943
String filename = "testSessionFile";
4044
File sessionFile = fileStore.getSessionFile(sessionId, filename);
4145
assertFalse(sessionFile.exists());
46+
assertNotNull(sessionFile.getParentFile());
4247
assertTrue(sessionFile.getParentFile().exists());
4348
assertEquals(sessionFile.getParentFile().getName(), sessionId);
4449

@@ -122,4 +127,10 @@ public void testGetReports() throws Exception {
122127
nativeReport.delete();
123128
assertEquals(0, fileStore.getNativeReports().size());
124129
}
130+
131+
public void testSanitizeName() {
132+
assertEquals(
133+
"com.google.my.awesome.app_big.stuff.Happens_Here123___",
134+
sanitizeName("com.google.my.awesome.app:big.stuff.Happens_Here123$%^"));
135+
}
125136
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistence.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,7 @@ public List<CrashlyticsReportWithSessionId> loadFinalizedReports() {
223223
}
224224

225225
private SortedSet<String> capAndGetOpenSessions(@Nullable String currentSessionId) {
226-
227-
// Fixes b/195664514
228-
fileStore.cleanupLegacyFiles();
226+
fileStore.cleanupPreviousFileSystems();
229227

230228
SortedSet<String> openSessionIds = getOpenSessionIds();
231229
if (currentSessionId != null) {

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/FileStore.java

+47-24
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
package com.google.firebase.crashlytics.internal.persistence;
1616

17+
import android.annotation.SuppressLint;
18+
import android.app.Application;
1719
import android.content.Context;
20+
import android.os.Build.VERSION;
21+
import android.os.Build.VERSION_CODES;
1822
import androidx.annotation.Nullable;
1923
import androidx.annotation.VisibleForTesting;
2024
import com.google.firebase.crashlytics.internal.Logger;
@@ -31,7 +35,7 @@
3135
*
3236
* <ul>
3337
* <li>"Common" files, that exist independent of a specific session id.
34-
* <li>"Open session" files, which contain a varierty of temporary files specific ot a Crashlytics
38+
* <li>"Open session" files, which contain a variety of temporary files specific ot a Crashlytics
3539
* session. These files may or may not eventually be combined into a Crashlytics crash report
3640
* file.
3741
* <li>"Report" files, which are processed reports, ready to be uploaded to Crashlytics servers.
@@ -48,46 +52,54 @@
4852
* convention, any use of new File(...) or similar outside of this class is a code smell.
4953
*/
5054
public class FileStore {
51-
52-
private static final String FILES_PATH = ".com.google.firebase.crashlytics.files.v1";
55+
private static final String CRASHLYTICS_PATH_V1 = ".com.google.firebase.crashlytics.files.v1";
56+
private static final String CRASHLYTICS_PATH_V2 = ".com.google.firebase.crashlytics.files.v2";
5357
private static final String SESSIONS_PATH = "open-sessions";
5458
private static final String NATIVE_SESSION_SUBDIR = "native";
5559
private static final String REPORTS_PATH = "reports";
5660
private static final String PRIORITY_REPORTS_PATH = "priority-reports";
5761
private static final String NATIVE_REPORTS_PATH = "native-reports";
5862

59-
private final File rootDir;
63+
private final File filesDir;
64+
private final File crashlyticsDir;
6065
private final File sessionsDir;
6166
private final File reportsDir;
6267
private final File priorityReportsDir;
6368
private final File nativeReportsDir;
6469

6570
public FileStore(Context context) {
66-
rootDir = prepareBaseDir(new File(context.getFilesDir(), FILES_PATH));
67-
sessionsDir = prepareBaseDir(new File(rootDir, SESSIONS_PATH));
68-
reportsDir = prepareBaseDir(new File(rootDir, REPORTS_PATH));
69-
priorityReportsDir = prepareBaseDir(new File(rootDir, PRIORITY_REPORTS_PATH));
70-
nativeReportsDir = prepareBaseDir(new File(rootDir, NATIVE_REPORTS_PATH));
71+
filesDir = context.getFilesDir();
72+
String crashlyticsPath =
73+
useV2FileSystem()
74+
? CRASHLYTICS_PATH_V2 + File.pathSeparator + sanitizeName(Application.getProcessName())
75+
: CRASHLYTICS_PATH_V1;
76+
crashlyticsDir = prepareBaseDir(new File(filesDir, crashlyticsPath));
77+
sessionsDir = prepareBaseDir(new File(crashlyticsDir, SESSIONS_PATH));
78+
reportsDir = prepareBaseDir(new File(crashlyticsDir, REPORTS_PATH));
79+
priorityReportsDir = prepareBaseDir(new File(crashlyticsDir, PRIORITY_REPORTS_PATH));
80+
nativeReportsDir = prepareBaseDir(new File(crashlyticsDir, NATIVE_REPORTS_PATH));
7181
}
7282

7383
@VisibleForTesting
7484
public void deleteAllCrashlyticsFiles() {
75-
recursiveDelete(rootDir);
85+
recursiveDelete(crashlyticsDir);
7686
}
7787

78-
public void cleanupLegacyFiles() {
79-
// Fixes b/195664514
80-
// :TODO: consider removing this method in mid 2023, to give all clients time to upgrade
81-
File[] legacyDirs =
82-
new File[] {
83-
new File(rootDir.getParent(), ".com.google.firebase.crashlytics"),
84-
new File(rootDir.getParent(), ".com.google.firebase.crashlytics-ndk")
85-
};
88+
/** Clean up files from previous file systems. */
89+
public void cleanupPreviousFileSystems() {
90+
// Clean up pre-versioned file systems.
91+
cleanupDir(new File(filesDir, ".com.google.firebase.crashlytics"));
92+
cleanupDir(new File(filesDir, ".com.google.firebase.crashlytics-ndk"));
8693

87-
for (File legacyDir : legacyDirs) {
88-
if (legacyDir.exists() && recursiveDelete(legacyDir)) {
89-
Logger.getLogger().d("Deleted legacy Crashlytics files from " + legacyDir.getPath());
90-
}
94+
// Clean up v1 file system.
95+
if (useV2FileSystem()) {
96+
cleanupDir(new File(filesDir, CRASHLYTICS_PATH_V1));
97+
}
98+
}
99+
100+
private void cleanupDir(File dir) {
101+
if (dir.exists() && recursiveDelete(dir)) {
102+
Logger.getLogger().d("Deleted previous Crashlytics file system: " + dir.getPath());
91103
}
92104
}
93105

@@ -103,12 +115,12 @@ static boolean recursiveDelete(File fileOrDirectory) {
103115

104116
/** @return internal File used by Crashlytics, that is not specific to a session */
105117
public File getCommonFile(String filename) {
106-
return new File(rootDir, filename);
118+
return new File(crashlyticsDir, filename);
107119
}
108120

109121
/** @return all common (non session specific) files matching the given filter. */
110122
public List<File> getCommonFiles(FilenameFilter filter) {
111-
return safeArrayToList(rootDir.listFiles(filter));
123+
return safeArrayToList(crashlyticsDir.listFiles(filter));
112124
}
113125

114126
private File getSessionDir(String sessionId) {
@@ -193,4 +205,15 @@ private static synchronized File prepareBaseDir(File file) {
193205
private static <T> List<T> safeArrayToList(@Nullable T[] array) {
194206
return (array == null) ? Collections.emptyList() : Arrays.asList(array);
195207
}
208+
209+
@SuppressLint("AnnotateVersionCheck")
210+
private static boolean useV2FileSystem() {
211+
return VERSION.SDK_INT >= VERSION_CODES.P;
212+
}
213+
214+
/** Replace potentially unsafe chars with underscores to make a safe file name. */
215+
@VisibleForTesting
216+
static String sanitizeName(String filename) {
217+
return filename.replaceAll("[^a-zA-Z0-9.]", "_");
218+
}
196219
}

0 commit comments

Comments
 (0)