Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1611e110 authored by Lee Shombert's avatar Lee Shombert
Browse files

Improve error reporting for open-database failures

If sqlite is unable to open a database file, the root-cause is often a
Posix errno.  This CL adds the errno error message to the exception
that is thrown.  This CL also ensures that the updated error message
makes it through the exception rewrite in SQLiteConnection.open().

Manually tested with a customized build that fails with ENOSPC and
ENOENT.

Test: atest
 * FrameworksCoreTests:android.database
 * CtsDatabaseTestCases
 * SQLiteDatabasePerfTest

Bug: 338297108
Flag: EXEMPT bugfix
Change-Id: I1fdad6c95f61a01345109d70318e4531245d1f0e
parent 2af3c050
Loading
Loading
Loading
Loading
+4 −6
Original line number Diff line number Diff line
@@ -239,8 +239,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
                    NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
                    mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
        } catch (SQLiteCantOpenDatabaseException e) {
            final StringBuilder message = new StringBuilder("Cannot open database '")
                    .append(file).append('\'')
            final StringBuilder message = new StringBuilder(e.getMessage())
                    .append(" '").append(file).append("'")
                    .append(" with flags 0x")
                    .append(Integer.toHexString(mConfiguration.openFlags));

@@ -265,12 +265,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
                    message.append(": File ").append(path).append(" is not readable");
                } else if (Files.isDirectory(path)) {
                    message.append(": Path ").append(path).append(" is a directory");
                } else {
                    message.append(": Unable to deduct failure reason");
                }
            } catch (Throwable th) {
                message.append(": Unable to deduct failure reason"
                        + " because filesystem couldn't be examined: ").append(th.getMessage());
                // Ignore any exceptions generated whilst attempting to create extended diagnostic
                // messages.
            }
            throw new SQLiteCantOpenDatabaseException(message.toString(), e);
        } finally {
+6 −1
Original line number Diff line number Diff line
@@ -134,10 +134,15 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
    String8 label(labelChars);
    env->ReleaseStringUTFChars(labelStr, labelChars);

    errno = 0;
    sqlite3* db;
    int err = sqlite3_open_v2(path.c_str(), &db, sqliteFlags, NULL);
    if (err != SQLITE_OK) {
        throw_sqlite3_exception_errcode(env, err, "Could not open database");
        if (errno == 0) {
            throw_sqlite3_exception(env, db, "could not open database");
        } else {
            throw_sqlite3_exception(env, db, strerror(errno));
        }
        return 0;
    }

+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.database.sqlite;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.content.Context;
import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.OpenParams;
import android.util.Log;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class SQLiteCantOpenDatabaseExceptionTest {
    private static final String TAG = "SQLiteCantOpenDatabaseExceptionTest";

    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();

    private File getDatabaseFile(String name) {
        mContext.deleteDatabase(name);

        // getDatabasePath() doesn't like a filename with a / in it, so we can't use it directly.
        return new File(mContext.getDatabasePath("a").getParentFile(), name);
    }

    private void callWithExpectedMessage(File file, String expectedMessagePattern) {
        try {
            SQLiteDatabase.openDatabase(file, new OpenParams.Builder().build());
            fail("SQLiteCantOpenDatabaseException was not thrown");
        } catch (SQLiteCantOpenDatabaseException e) {
            Log.i(TAG, "Caught expected exception: " + e.getMessage());
            assertTrue(e.getMessage().matches(expectedMessagePattern));
        }
    }

    /** DB's directory doesn't exist. */
    @Test
    public void testDirectoryDoesNotExist() {
        final File file = getDatabaseFile("nonexisitentdir/mydb.db");

        callWithExpectedMessage(file, ".*: Directory .* doesn't exist");
    }

    /** File doesn't exist */
    @Test
    public void testFileDoesNotExist() {
        final File file = getDatabaseFile("mydb.db");

        callWithExpectedMessage(file, ".*: File .* doesn't exist");
    }

    /** File exists, but not readable. */
    @Test
    public void testFileNotReadable() {
        final File file = getDatabaseFile("mydb.db");

        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(file, null);
        db.close();

        file.setReadable(false);

        callWithExpectedMessage(file, ".*: File .* not readable");
    }

    /** Directory with the given name exists already. */
    @Test
    public void testPathIsADirectory() {
        final File file = getDatabaseFile("mydb.db");

        file.mkdirs();

        callWithExpectedMessage(file, ".*: Path .* is a directory");
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.util.Log;

import androidx.test.filters.SmallTest;