Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions FirebaseMessaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Fix missing database crash on launch. (#14880)

# 12.1.0
- [fixed] Fix Xcode 26 crash from missing `NSUserActivityTypeBrowsingWeb`
symbol. Note that this fix isn't in the 12.1.0 zip and Carthage
Expand Down
83 changes: 46 additions & 37 deletions FirebaseMessaging/Sources/FIRMessagingRmqManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -493,53 +493,62 @@ - (void)openDatabase {
dispatch_async(_databaseOperationQueue, ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *path = [self pathForDatabase];

BOOL didOpenDatabase = YES;
if (![fileManager fileExistsAtPath:path]) {
// We've to separate between different versions here because of backward compatibility issues.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
BOOL fileExists = [fileManager fileExistsAtPath:path];

// Ensure the directory exists before trying to create the database if the file doesn't exist.
if (!fileExists) {
NSString *directory = [path stringByDeletingLastPathComponent];
if (![fileManager fileExistsAtPath:directory]) {
NSError *error;
if (![fileManager createDirectoryAtPath:directory
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
NSString *errorMessage = [NSString
stringWithFormat:@"Could not create RMQ database directory at path %@, error: %@",
directory, error];
FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorCreatingDatabase,
@"%@", errorMessage);
NSAssert(NO, errorMessage);
return;
}
}
}
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
#ifdef SQLITE_OPEN_FILEPROTECTION_NONE
flags |= SQLITE_OPEN_FILEPROTECTION_NONE;
flags |= SQLITE_OPEN_FILEPROTECTION_NONE;
#endif
int result = sqlite3_open_v2([path UTF8String], &self -> _database, flags, NULL);
if (result != SQLITE_OK) {
NSString *errorString = FIRMessagingStringFromSQLiteResult(result);
NSString *errorMessage = [NSString
stringWithFormat:@"Could not open existing RMQ database at path %@, error: %@", path,
errorString];
FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase,
@"%@", errorMessage);
NSAssert(NO, errorMessage);
return;
int result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL);
if (result != SQLITE_OK) {
NSString *errorString = FIRMessagingStringFromSQLiteResult(result);
NSString *errorMessage =
[NSString stringWithFormat:@"Could not open or create RMQ database at path %@, error: %@",
path, errorString];
FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase,
@"%@", errorMessage);

// If the file existed, it might be corrupt. Let's remove it and fail.
if (fileExists) {
// This is synchronous, but it's on the database queue, so it's safe.
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

It's good practice to handle the error that can occur when removing the item at the specified path. While the NSAssert will likely crash the app in debug builds, logging the error from removeItemAtPath:error: can provide valuable diagnostic information, especially in release builds where assertions are disabled and the app might attempt to recover on the next launch.

        NSError *removeError = nil;
        if (![[NSFileManager defaultManager] removeItemAtPath:path error:&removeError]) {
          FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase,
                                  @"Failed to remove corrupt database file at %@: %@", path,
                                  removeError);
        }

}
NSAssert(NO, errorMessage);
return;
}

if (!fileExists) {
// New database, create tables.
[self createTableWithName:kTableOutgoingRmqMessages command:kCreateTableOutgoingRmqMessages];

[self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId];
[self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds];
} else {
// Calling sqlite3_open should create the database, since the file doesn't exist.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
#ifdef SQLITE_OPEN_FILEPROTECTION_NONE
flags |= SQLITE_OPEN_FILEPROTECTION_NONE;
#endif
int result = sqlite3_open_v2([path UTF8String], &self -> _database, flags, NULL);
if (result != SQLITE_OK) {
NSString *errorString = FIRMessagingStringFromSQLiteResult(result);
NSString *errorMessage =
[NSString stringWithFormat:@"Could not create RMQ database at path %@, error: %@", path,
errorString];
FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorCreatingDatabase,
@"%@", errorMessage);
NSAssert(NO, errorMessage);
Comment on lines -528 to -533
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More investigation needed. Converted to draft.

From linked issue:

Fatal Exception: NSInternalInconsistencyException
Could not create RMQ database at path /var/mobile/Containers/Data/Application/372D4296-3F35-46FD-B3C9-0A20877B8378/Library/Application Support/Google/FirebaseMessaging/rmq2.sqlite, error: 14 - unable to open database file

This highlighted else clause is where the execution flow reaches the assertion. The assertion's error message is only used here. Entering the else clause means database path exists, so the error has to do with opening a database we know exists in the filesystem.

didOpenDatabase = NO;
} else {
[self updateDBWithStringRmqID];
}
// Existing database, update schema if needed.
[self updateDBWithStringRmqID];
}

if (didOpenDatabase) {
[self createTableWithName:kTableSyncMessages command:kCreateTableSyncMessages];
}
// This table should exist in both new and old databases.
[self createTableWithName:kTableSyncMessages command:kCreateTableSyncMessages];
});
}

Expand Down
Loading