realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.46k stars 1.75k forks source link

Caused by: io.realm.exceptions.RealmFileException: Unable to open a realm at path '/data/data/com.appname/files/notes.db': Realm file has bad size. #5500

Open shanraisshan opened 6 years ago

shanraisshan commented 6 years ago

Goal:

App has backup functionality,

When click on backup: App copies/replaces database from Internal Storage to External Storage

When click on restore: App copies/replaces database from External Storage to Internal Storage

The app is working great on production.

Issue:

I personally was using the debug(not release) build of the app on my phone.

I had around 500 messages that was backed up on External Storage.

Today, I accidentally uninstall the app. :cry:

Expected Results:

Now, on installing latest version 2.2.0.0(either debug/release build), it should automatically backup the messages, but its is giving following error.

I am not sure know whether the notes.db file is corrupted or what happened

Stack Trace:

FATAL EXCEPTION: main
                                             Process: com.appname, PID: 6831
                                             java.lang.RuntimeException: Unable to start activity ComponentInfo{com.appname/com.appname.mvp.activities.Chat}: io.realm.exceptions.RealmFileException: Unable to open a realm at path '/data/data/com.appname/files/notes.db': Realm file has bad size. (Realm file has bad size) (/data/data/com.appname/files/notes.db) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 252 Kind: ACCESS_ERROR.
                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                 at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                 at android.os.Looper.loop(Looper.java:148)
                                                 at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                              Caused by: io.realm.exceptions.RealmFileException: Unable to open a realm at path '/data/data/com.appname/files/notes.db': Realm file has bad size. (Realm file has bad size) (/data/data/com.appname/files/notes.db) in /home/cc/repo/realm/release/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 252 Kind: ACCESS_ERROR.
                                                 at io.realm.internal.SharedRealm.nativeGetSharedRealm(Native Method)
                                                 at io.realm.internal.SharedRealm.<init>(SharedRealm.java:186)
                                                 at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:239)
                                                 at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:202)
                                                 at io.realm.RealmCache.doCreateRealmOrGetFromCache(RealmCache.java:298)
                                                 at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:284)
                                                 at io.realm.Realm.getInstance(Realm.java:301)
                                                 at com.appname.mvp.activities.Chat.getChatList(Chat.java:447)
                                                 at com.appname.mvp.activities.Chat.onCreate(Chat.java:95)
                                                 at android.app.Activity.performCreate(Activity.java:6251)
                                                 at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
                                                 at android.app.ActivityThread.-wrap11(ActivityThread.java) 
                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
                                                 at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                 at android.os.Looper.loop(Looper.java:148) 
                                                 at android.app.ActivityThread.main(ActivityThread.java:5417) 
                                                 at java.lang.reflect.Method.invoke(Native Method) 
                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Code Sample:

Code for backup

public static void backupDB() {
    Realm realm = Realm.getInstance(DBUtil.getRealmConfiguration());
    File f = PathUtil.createExternalDatabaseFile();
    StorageUtil.delete(f); //[mandatory_: java.lang.IllegalArgumentException: The destination file must not exist]
    realm.writeCopyTo(f);
    realm.close();
}

Code for restore

public static void restoreDB() {
    //realm.getPath()  ->   /data/data/com.appname/files/notes.db
    Realm realm = Realm.getInstance(DBUtil.getRealmConfiguration());
    File fromFile = PathUtil.getExternalDatabaseFile();
    File toFile = new File(realm.getPath());
    if (fromFile.exists())
        StorageUtil.copy(fromFile, toFile);
    realm.close();
}

Utilities

public static RealmConfiguration getRealmConfiguration() {
    return new RealmConfiguration
            .Builder()
            .name("notes.db")
            .schemaVersion(1)
            .modules(new SchemaNotesDB())
            .build();
}

public static void copy(File fromFile, File toFile) {
    try {
        FileInputStream is = new FileInputStream(fromFile);
        FileChannel src = is.getChannel();
        FileOutputStream os = new FileOutputStream(toFile);
        FileChannel dst = os.getChannel();
        dst.transferFrom(src, 0, src.size());
        src.close();    is.close();
        dst.close();    os.close();
    } catch (Exception ignored) {
    }
}

Version of Realm and tooling

Realm version(s): 4.1.1

Realm sync feature enabled: false

Android Studio version: 3.0.0

Which Android version and device: ? Nexus 5 (Marshmallow), Nexus 6P (Oreo)

Realm Backed Up Database Size (External Storage): 83.8 KB

Zhuinden commented 6 years ago
Realm realm = Realm.getInstance(DBUtil.getRealmConfiguration());
    StorageUtil.copy(fromFile, toFile);

Do not overwrite open Realm file.

shanraisshan commented 6 years ago

well its a production code, and is working well on 2.2.0.0

public static void restoreDB() {
    //realm.getPath()  ->   /data/data/com.appname/files/notes.db
    Realm realm = Realm.getInstance(DBUtil.getRealmConfiguration());
    File fromFile = PathUtil.getExternalDatabaseFile();
    File toFile = new File(realm.getPath());
    if (fromFile.exists())
        StorageUtil.copy(fromFile, toFile);
    realm.close();
}

Nevertheless, changing it to below, is not helping either

public static void restoreDB() {
    RealmConfiguration configuration = DBUtil.getRealmConfiguration();
    Realm realm = Realm.getInstance(configuration);
    String path = realm.getPath();
    realm.close();
    Log.e("---->", Realm.getGlobalInstanceCount(configuration) + ""); //0
    File fromFile = PathUtil.getExternalDatabaseFile();
    File toFile = new File(path);
    if (fromFile.exists())
        StorageUtil.copy(fromFile, toFile);
}

Same error, I think there is an issue with my backed up database at external storage

Zhuinden commented 6 years ago

Have you tried opening the external storage Realm file in the Realm Browser?

Or as an asset file directly (instead of through restoration)?

shanraisshan commented 6 years ago

I am on windows, I'll try in few hours, then comment.

shanraisshan commented 6 years ago

It is giving Please enter valid encryption key for realm file

Is there any way from which I can recover my data from database of version 1.0.0? How to check that whether database file is not corrupted?

beeender commented 6 years ago
public static void restoreDB() {
    //realm.getPath()  ->   /data/data/com.appname/files/notes.db
    Realm realm = Realm.getInstance(DBUtil.getRealmConfiguration());
    File fromFile = PathUtil.getExternalDatabaseFile();
    File toFile = new File(realm.getPath());
    if (fromFile.exists())
        StorageUtil.copy(fromFile, toFile);
    realm.close();
}

Your restore function has a very big chance to damage the db file. Do never ever delete/overwrite the Realm file while there are Realm instances opened.

We might be able to supply a safe restore function with proper locks.

beeender commented 6 years ago

It might make sense to add a new API

Realm.restore(RealmConfiguaration config, File backupRealmFile)

for the restore purpose. @realm/java ?

shanraisshan commented 6 years ago

How to check that whether my backed up database file (external storage) is corrupted or not?

Is there any way to view data from corrupted file, I can see gibberish + messages when opening db in text editor.

beeender commented 6 years ago

@shanraisshan

We will supply an API for restoring, something like:

RealmConfiguration.Builder()
  .restoreFile(File path)
  .build();

It will restore the Realm file if the config.getPath() doesn't exist.

How to check that whether my backed up database file (external storage) is corrupted or not?

It should not be corrupted if you are using Realm.writeCopyTo() and didn't change it manually. But if it is corrupted, getInstance() will throw.

shanraisshan commented 6 years ago

Okay, I will wait, I hope everything goes well. Thank you.

nhachicha commented 6 years ago

@beeender we can return aSHA hash of the backup file, the user can provide it back when trying to restore again, this way we can ensure that the backup file was not tampered with.

// backup
String hash = realm.writeCopyTo(path);
// developer responsibility save the hash somewhere (Global SharedPref, text file, Drive etc...) 

// restore
RealmConfiguration.Builder()
  .restoreFile(File path, hash/*optional*/)
  .build();
shanraisshan commented 6 years ago

this way we can ensure that the backup file was not tampered with. @nhachicha is there any issue if file is tampered?

For Example: If user backed up a database with 1000 records, open it on MAC browser, insert 2500 more records, and wanted it to restore database with 3500 records.

Is this a problem?

nhachicha commented 6 years ago

@shanraisshan In theory, no, the hash should be used optionally as I mentioned, if you explicitly update the Realm then you lose the feature obviously.

The idea here is to give the developer options whether he/she wants to use the hash or not

(Note: you can still hash the file manually after you update the Realm shasum -a 256 backup.realm then use it to restore the backup)