diff --git a/FirebaseDatabase/CHANGELOG.md b/FirebaseDatabase/CHANGELOG.md index 3aec4b057b5..180a5928fa7 100644 --- a/FirebaseDatabase/CHANGELOG.md +++ b/FirebaseDatabase/CHANGELOG.md @@ -1,4 +1,5 @@ # 12.7.0 +- [fixed] Fix `Fatal Exception: FirebaseDatabasePersistenceFailure`. (#4493) - [fixed] Concurrency crash in FView. (#15514) # 11.9.0 diff --git a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m index 6a382db05f9..3823d07a8ff 100644 --- a/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m +++ b/FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m @@ -977,17 +977,40 @@ - (id)deserializePrimitive:(NSData *)data { } + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup { + NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; - BOOL success = - [[NSFileManager defaultManager] createDirectoryAtPath:path - withIntermediateDirectories:YES - attributes:nil - error:&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}]; + userInfo:@{ + @"path" : path, + @"error" : error ?: [NSNull null] + }]; + } + + // Now, ensure the file protection attribute is set. This will apply it + // whether the directory was just created or already existed. Note, this + // attribute has no effect on simulators. + NSDictionary *attributes = @{ + NSFileProtectionKey : + NSFileProtectionCompleteUntilFirstUserAuthentication + }; + success = [fileManager setAttributes:attributes + ofItemAtPath:path + error:&error]; + if (!success) { + FFWarn(@"I-RDB076036", + @"Failed to set file protection attribute on persistence " + @"directory: %@", + error); } if (markAsDoNotBackup) { @@ -1000,9 +1023,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..823b693cf8c 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,49 @@ - (void)testRemoveTrackedQueryRemovesTrackedQueryKeys { ([NSSet setWithArray:@[ @"b", @"c" ]])); } +- (void)testEnsureDirSetsCorrectFileProtection { + NSString *testDirName = + [NSString stringWithFormat:@"fdb_persistence_test_%lu", (unsigned long)arc4random()]; + NSString *testPath = [NSTemporaryDirectory() stringByAppendingPathComponent:testDirName]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // --- Test creation --- + [fileManager removeItemAtPath:testPath error:nil]; + [FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO]; + + NSError *error = nil; + NSDictionary *attributes = [fileManager attributesOfItemAtPath:testPath + error:&error]; + XCTAssertNil(error, @"Failed to get attributes of directory: %@", error); + +#if !TARGET_OS_SIMULATOR + // On a physical device, file protection should be set. + XCTAssertEqualObjects(attributes[NSFileProtectionKey], + NSFileProtectionCompleteUntilFirstUserAuthentication); +#else + 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 + [fileManager removeItemAtPath:testPath error:nil]; +} @end