From 637b4ea0180b80bcf722a35498f84caedae2e730 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 30 Nov 2025 10:19:23 -0800 Subject: [PATCH 1/5] [RTDB] Fix Fatal Exception: FirebaseDatabasePersistenceFailure --- FirebaseDatabase/CHANGELOG.md | 3 ++ .../Persistence/FLevelDBStorageEngine.m | 16 +++++--- .../Tests/Unit/FLevelDBStorageEngineTests.m | 37 ++++++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/FirebaseDatabase/CHANGELOG.md b/FirebaseDatabase/CHANGELOG.md index d2a83c45116..a0cc8b5e9b3 100644 --- a/FirebaseDatabase/CHANGELOG.md +++ b/FirebaseDatabase/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Fix `Fatal Exception: FirebaseDatabasePersistenceFailure`. (#4493) + # 11.9.0 - [fixed] Fix connection failure issue introduced in 10.27.0 by restoring the Socket Rocket implementation instead of `NSURLSessionWebSocket`. Note that diff --git a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m index 6a382db05f9..695ff9d9446 100644 --- a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m +++ b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m @@ -977,17 +977,24 @@ - (id)deserializePrimitive:(NSData *)data { } + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { - NSError *error; + NSError *error = nil; + NSDictionary *attributes = @{ + NSFileProtectionKey : + NSFileProtectionCompleteUntilFirstUserAuthentication + }; BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES - attributes:nil + attributes:attributes error:&error]; if (!success) { @throw [NSException exceptionWithName:@"FailedToCreatePersistenceDir" reason:@"Failed to create persistence directory." - userInfo:@{@"path" : path}]; + userInfo:@{ + @"path" : path, + @"error" : error ? error : [NSNull null] + }]; } if (markAsDoNotBackup) { @@ -1000,9 +1007,6 @@ + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { @"I-RDB076035", @"Failed to mark firebase database folder as do not backup: %@", error); - [NSException raise:@"Error marking as do not backup" - format:@"Failed to mark folder %@ as do not backup", - firebaseDirURL]; } } } diff --git a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m index dac30ab295e..c074b8c961b 100644 --- a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m +++ b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m @@ -25,8 +25,11 @@ #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h" #import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h" -@interface FLevelDBStorageEngineTests : XCTestCase +@interface FLevelDBStorageEngine (Tests) ++ (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup; +@end +@interface FLevelDBStorageEngineTests : XCTestCase @end @implementation FLevelDBStorageEngineTests @@ -685,4 +688,36 @@ - (void)testRemoveTrackedQueryRemovesTrackedQueryKeys { ([NSSet setWithArray:@[ @"b", @"c" ]])); } +- (void)testEnsureDirSetsCorrectFileProtection { + NSString *testDirName = + [NSString stringWithFormat:@"fdb_persistence_test_%lu", (unsigned long)arc4random()]; + NSString *testPath = [NSTemporaryDirectory() stringByAppendingPathComponent:testDirName]; + + // Ensure the directory doesn't exist before the test + [[NSFileManager defaultManager] removeItemAtPath:testPath error:nil]; + + // Call the method to create the directory + [FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO]; + + // Get the attributes of the created directory + NSError *error = nil; + NSDictionary *attributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:testPath error:&error]; + + // Assert that the file protection attribute is correct + XCTAssertNil(error, @"Failed to get attributes of directory: %@", error); + +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR + // On a physical iOS device, file protection should be set. + XCTAssertEqualObjects(attributes[NSFileProtectionKey], + NSFileProtectionCompleteUntilFirstUserAuthentication); +#else + // In the simulator or on other platforms, file protection is not supported, so the key + // should be nil. + XCTAssertNil(attributes[NSFileProtectionKey]); +#endif + + // Clean up + [[NSFileManager defaultManager] removeItemAtPath:testPath error:nil]; +} @end From 91f4b097acd82895720cc5dbb8c70a8778c643b0 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 30 Nov 2025 14:13:28 -0800 Subject: [PATCH 2/5] Fix test for macos --- FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m index c074b8c961b..2424933a8d1 100644 --- a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m +++ b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m @@ -707,13 +707,12 @@ - (void)testEnsureDirSetsCorrectFileProtection { // Assert that the file protection attribute is correct XCTAssertNil(error, @"Failed to get attributes of directory: %@", error); -#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR - // On a physical iOS device, file protection should be set. +#if !TARGET_OS_SIMULATOR + // On a physical device, file protection should be set. XCTAssertEqualObjects(attributes[NSFileProtectionKey], NSFileProtectionCompleteUntilFirstUserAuthentication); #else - // In the simulator or on other platforms, file protection is not supported, so the key - // should be nil. + // In the simulator, file protection is not supported, so the key should be nil. XCTAssertNil(attributes[NSFileProtectionKey]); #endif From c2413f2235812455e545ad1b6b673796ca95ac67 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 1 Dec 2025 19:45:52 -0800 Subject: [PATCH 3/5] review --- .../Persistence/FLevelDBStorageEngine.m | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m index 695ff9d9446..8aca9fcaa30 100644 --- a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m +++ b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m @@ -977,26 +977,47 @@ - (id)deserializePrimitive:(NSData *)data { } + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { - NSError *error = nil; - NSDictionary *attributes = @{ - NSFileProtectionKey : - NSFileProtectionCompleteUntilFirstUserAuthentication - }; - BOOL success = - [[NSFileManager defaultManager] createDirectoryAtPath:path - withIntermediateDirectories:YES - attributes:attributes - error:&error]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error; + + // Create the directory if it doesn't exist. This call is a no-op if it + // already exists. + BOOL success = [fileManager createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:&error]; if (!success) { @throw [NSException exceptionWithName:@"FailedToCreatePersistenceDir" reason:@"Failed to create persistence directory." userInfo:@{ @"path" : path, - @"error" : error ? error : [NSNull null] + @"error" : error ?: [NSNull null] }]; } +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION || TARGET_OS_OSX || \ + TARGET_OS_MACCATALYST + // Now, ensure the file protection attribute is set. This will apply it + // whether the directory was just created or already existed. This attribute + // is ignored on systems that do not support it. + NSDictionary *attributes = @{ + NSFileProtectionKey : + NSFileProtectionCompleteUntilFirstUserAuthentication + }; + success = [fileManager setAttributes:attributes + ofItemAtPath:path + error:&error]; + if (!success) { + // This is not a fatal error, as file protection may not be supported on + // all OS versions. + FFWarn(@"I-RDB076036", + @"Failed to set file protection attribute on persistence " + @"directory: %@", + error); + } +#endif + if (markAsDoNotBackup) { NSURL *firebaseDirURL = [NSURL fileURLWithPath:path]; success = [firebaseDirURL setResourceValue:@YES From 5874de730ab0c1d0b3581b360d99c1b99c843f1c Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 1 Dec 2025 19:59:15 -0800 Subject: [PATCH 4/5] Review --- .../Tests/Unit/FLevelDBStorageEngineTests.m | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m index 2424933a8d1..823b693cf8c 100644 --- a/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m +++ b/FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m @@ -692,19 +692,15 @@ - (void)testEnsureDirSetsCorrectFileProtection { NSString *testDirName = [NSString stringWithFormat:@"fdb_persistence_test_%lu", (unsigned long)arc4random()]; NSString *testPath = [NSTemporaryDirectory() stringByAppendingPathComponent:testDirName]; + NSFileManager *fileManager = [NSFileManager defaultManager]; - // Ensure the directory doesn't exist before the test - [[NSFileManager defaultManager] removeItemAtPath:testPath error:nil]; - - // Call the method to create the directory + // --- Test creation --- + [fileManager removeItemAtPath:testPath error:nil]; [FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO]; - // Get the attributes of the created directory NSError *error = nil; - NSDictionary *attributes = - [[NSFileManager defaultManager] attributesOfItemAtPath:testPath error:&error]; - - // Assert that the file protection attribute is correct + NSDictionary *attributes = [fileManager attributesOfItemAtPath:testPath + error:&error]; XCTAssertNil(error, @"Failed to get attributes of directory: %@", error); #if !TARGET_OS_SIMULATOR @@ -712,11 +708,29 @@ - (void)testEnsureDirSetsCorrectFileProtection { XCTAssertEqualObjects(attributes[NSFileProtectionKey], NSFileProtectionCompleteUntilFirstUserAuthentication); #else - // In the simulator, file protection is not supported, so the key should be nil. XCTAssertNil(attributes[NSFileProtectionKey]); #endif + // --- Test update on existing directory --- +#if !TARGET_OS_SIMULATOR + // This part of the test is only relevant on devices where file protection is supported. + [fileManager removeItemAtPath:testPath error:nil]; + NSDictionary *initialAttributes = @{NSFileProtectionKey : NSFileProtectionNone}; + XCTAssertTrue([fileManager createDirectoryAtPath:testPath + withIntermediateDirectories:YES + attributes:initialAttributes + error:&error], + @"Failed to create directory for update test: %@", error); + + [FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO]; + + attributes = [fileManager attributesOfItemAtPath:testPath error:&error]; + XCTAssertNil(error, @"Failed to get attributes after update: %@", error); + XCTAssertEqualObjects(attributes[NSFileProtectionKey], + NSFileProtectionCompleteUntilFirstUserAuthentication); +#endif // !TARGET_OS_SIMULATOR + // Clean up - [[NSFileManager defaultManager] removeItemAtPath:testPath error:nil]; + [fileManager removeItemAtPath:testPath error:nil]; } @end From f77937eb8eacbb8a9cf2bbd807907e6d906605a6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 2 Dec 2025 17:39:09 -0500 Subject: [PATCH 5/5] review --- .../Sources/Persistence/FLevelDBStorageEngine.m | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m index 8aca9fcaa30..3823d07a8ff 100644 --- a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m +++ b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m @@ -996,11 +996,9 @@ + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { }]; } -#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION || TARGET_OS_OSX || \ - TARGET_OS_MACCATALYST // Now, ensure the file protection attribute is set. This will apply it - // whether the directory was just created or already existed. This attribute - // is ignored on systems that do not support it. + // whether the directory was just created or already existed. Note, this + // attribute has no effect on simulators. NSDictionary *attributes = @{ NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication @@ -1009,14 +1007,11 @@ + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { ofItemAtPath:path error:&error]; if (!success) { - // This is not a fatal error, as file protection may not be supported on - // all OS versions. FFWarn(@"I-RDB076036", @"Failed to set file protection attribute on persistence " @"directory: %@", error); } -#endif if (markAsDoNotBackup) { NSURL *firebaseDirURL = [NSURL fileURLWithPath:path];