Skip to content
Open
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
1 change: 1 addition & 0 deletions FirebaseDatabase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Unreleased
- [fixed] Fix `Fatal Exception: FirebaseDatabasePersistenceFailure`. (#4493)
- [fixed] Concurrency crash in FView. (#15514)

# 11.9.0
Expand Down
43 changes: 34 additions & 9 deletions FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m
Copy link
Member

@ncooke3 ncooke3 Dec 1, 2025

Choose a reason for hiding this comment

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

When the directory already exists, it doesn't sound like the file protection attribute will be updated: true if createIntermediates is set and the directory already exists (from docs).

To handle this case, I think we need to instead set the attribute via this API: https://developer.apple.com/documentation/foundation/filemanager/setattributes(_:ofitematpath:)?language=objc

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated. PTAL

Original file line number Diff line number Diff line change
Expand Up @@ -977,18 +977,46 @@ - (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]
}];
}

#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];
Expand All @@ -1000,9 +1028,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];
}
}
}
Expand Down
50 changes: 49 additions & 1 deletion FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<NSFileAttributeKey, id> *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
Loading