Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using RLMPathForFile open() failed: operation not permitted. #3308

Closed
AndrewBarba opened this issue Mar 10, 2016 · 6 comments
Closed

Using RLMPathForFile open() failed: operation not permitted. #3308

AndrewBarba opened this issue Mar 10, 2016 · 6 comments
Assignees
Labels

Comments

@AndrewBarba
Copy link

Goals

Open a realm that is not the default realm by using RLMPathForFile

Expected Results

The realm opens successfully with write permissions

Actual Results

The thread crashes with:

fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=1 "open() failed: Operation not permitted" UserInfo={NSLocalizedDescription=open() failed: Operation not permitted, Error Code=1}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.1.101.15/src/swift/stdlib/public/core/ErrorType.swift, line 50

Steps to Reproduce

I am having trouble reproducing the issue. I haven't actually seen it happen (and testers haven't reported crashing) which leads me to believe this is happening in the background when iOS launches the app to perform a background fetch. Could write permissions be different in a background state? I remember when iOS 7 launched there were a lot of issues with the Keychain and background fetches, I wonder if something similar is happening here.

Code Sample

Basic initialization of the Realm. SDKNameSpace is really just a bundle id like "com.company.App":

public static func setup(schemaVersion schemaVersion: UInt64? = nil, inMemoryIdentifier: String? = nil) {
    Realm.Configuration.defaultConfiguration = Realm.Configuration(
        path: RLMRealmPathForFile("\(SDKNameSpace).realm"),
        schemaVersion: schemaVersion ?? RealmSchemaVersion,
        migrationBlock: migrationBlock,
        inMemoryIdentifier: inMemoryIdentifier
    )
}

Additional Notes

This issue seems interesting: #3264

I noticed that on startup Realm was slow to load so in application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) I call the setup function above and then immediately after, run an empty write to the realm which seems to "pre-boot" it in a sense.

Edit: One thing I just thought of, I call that setup function in both application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) and application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) and I wonder if that is causing a timing issue. I added a guard to make sure the SDK is only initialized once, I'll report back any additional findings.

Version of Realm and Tooling

RealmSwift 0.93.0
XCode 7.2.1
iOS 9.2.1, iOS 9.3.0

@mrackwitz
Copy link
Contributor

Do you change the file encryption attributes for the directory in which the Realm file and all auxiliary files are contained in? Realm is creating and deleting auxiliary files adjacent to the specified path of your database. Since iOS 8, file encryption is enabled by default. So you have to make sure that this directory can be written while the app is in the background / the device is locked by disabling file encryption via NSFileProtectionKey = NSFileProtectionNone.

@AndrewBarba
Copy link
Author

I didn't touch anything related to data protection classes but have since come across #3217, #3223, and #3225. #3217 seems to be the same issue. I'm not sure how to set a data protection class on just a single directory and then store my realm in that directory, and I really don't like the idea of changing properties on the aux files so instead I added the data protection entitlements to provisioning profile and am setting it to NSFileProtectionCompleteUnlessOpen which Apple explicitly states is useful for operating on documents in a background state.

@mrackwitz
Copy link
Contributor

That won't work as some of the auxiliary files are created on demand when writing to a Realm. So even when the Realm was open before and stays accessible, you don't have a guarantee so far that all necessary auxiliary files were already created. That might happen currently just on demand, when they become necessary.

@AndrewBarba
Copy link
Author

Okay good to know. I will change to NSFileProtectionNone for now. Can you confirm that changing in the entitlements file is the right way to go about this until you guys add support to RLMConfiguration?

@AndrewBarba
Copy link
Author

Actually turns out NSFileProtectionNone is not supported in the entitlements, I just got an error uploading to TestFlight. Do you have other suggestions? Perhaps NSFileProtectionCompleteUntilFirstUserAuthentication? That seems like it will get me closest to what I need.

Also added the following to application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void)

// We might not have access to Realm
guard application.protectedDataAvailable else {
    return completionHandler(.NoData)
}

// Make sure SDK is initialized
setupSDKs()

@mrackwitz mrackwitz self-assigned this Mar 11, 2016
@jpsim
Copy link
Contributor

jpsim commented Mar 12, 2016

@AndrewBarba we're not asking that you set NSFileProtectionNone in your app's entitlements but rather set that as file attributes for your Realm file's parent directory.

@TimOliver has been working on some documentation around this, but it's not live yet on our site since it's still pending review, but I'll post it here because I think it would be useful to you:

Using Realm with Background App Refresh

On iOS 8 and above, files inside apps are automatically encrypted using NSFileProtection whenever the device is locked. If your app attempts to do any work involving Realm while the device is locked and its parent directory isn't set to NSFileProtectionNone, an open() failed: Operation not permitted exception will be thrown.

To avoid this, it is necessary to ensure the NSFileProtectionNone file protection attribute is applied to both the Realm file itself and its auxiliary files. Since the auxiliary files can sometimes be lazily created and deleted mid-operation, it is recommended to apply the file protection attribute to the parent folder containing these Realm files to ensure the attribute is properly applied to all of them.

let realm = try! Realm()

// Get our Realm file's parent directory
let folderPath = (realm.configuration.path! as NSString).stringByDeletingLastPathComponent

// Disable file protection for this directory
try! NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone],
                                                  ofItemAtPath: folderPath)

If your app contains other files for which you don't want to disable protection, we recommended that these Realm files be moved to their own separate directory.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants