michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "dlfcn.h" michael@0: #include "APKOpen.h" michael@0: #include "ElfLoader.h" michael@0: #include "SQLiteBridge.h" michael@0: michael@0: #ifdef DEBUG michael@0: #define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x) michael@0: #else michael@0: #define LOG(x...) michael@0: #endif michael@0: michael@0: #define SQLITE_WRAPPER_INT(name) name ## _t f_ ## name; michael@0: michael@0: SQLITE_WRAPPER_INT(sqlite3_open) michael@0: SQLITE_WRAPPER_INT(sqlite3_errmsg) michael@0: SQLITE_WRAPPER_INT(sqlite3_prepare_v2) michael@0: SQLITE_WRAPPER_INT(sqlite3_bind_parameter_count) michael@0: SQLITE_WRAPPER_INT(sqlite3_bind_text) michael@0: SQLITE_WRAPPER_INT(sqlite3_step) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_count) michael@0: SQLITE_WRAPPER_INT(sqlite3_finalize) michael@0: SQLITE_WRAPPER_INT(sqlite3_close) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_name) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_type) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_blob) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_bytes) michael@0: SQLITE_WRAPPER_INT(sqlite3_column_text) michael@0: SQLITE_WRAPPER_INT(sqlite3_changes) michael@0: SQLITE_WRAPPER_INT(sqlite3_last_insert_rowid) michael@0: michael@0: void setup_sqlite_functions(void *sqlite_handle) michael@0: { michael@0: #define GETFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(sqlite_handle, #name) michael@0: GETFUNC(sqlite3_open); michael@0: GETFUNC(sqlite3_errmsg); michael@0: GETFUNC(sqlite3_prepare_v2); michael@0: GETFUNC(sqlite3_bind_parameter_count); michael@0: GETFUNC(sqlite3_bind_text); michael@0: GETFUNC(sqlite3_step); michael@0: GETFUNC(sqlite3_column_count); michael@0: GETFUNC(sqlite3_finalize); michael@0: GETFUNC(sqlite3_close); michael@0: GETFUNC(sqlite3_column_name); michael@0: GETFUNC(sqlite3_column_type); michael@0: GETFUNC(sqlite3_column_blob); michael@0: GETFUNC(sqlite3_column_bytes); michael@0: GETFUNC(sqlite3_column_text); michael@0: GETFUNC(sqlite3_changes); michael@0: GETFUNC(sqlite3_last_insert_rowid); michael@0: #undef GETFUNC michael@0: } michael@0: michael@0: static bool initialized = false; michael@0: static jclass stringClass; michael@0: static jclass objectClass; michael@0: static jclass byteBufferClass; michael@0: static jclass cursorClass; michael@0: static jmethodID jByteBufferAllocateDirect; michael@0: static jmethodID jCursorConstructor; michael@0: static jmethodID jCursorAddRow; michael@0: michael@0: static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, michael@0: jobjectArray jParams, jlongArray jQueryRes); michael@0: michael@0: static void throwSqliteException(JNIEnv* jenv, const char* aFormat, ...) michael@0: { michael@0: va_list ap; michael@0: va_start(ap, aFormat); michael@0: char* msg = nullptr; michael@0: vasprintf(&msg, aFormat, ap); michael@0: LOG("Error in SQLiteBridge: %s\n", msg); michael@0: JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", msg); michael@0: free(msg); michael@0: va_end(ap); michael@0: } michael@0: michael@0: static void michael@0: JNI_Setup(JNIEnv* jenv) michael@0: { michael@0: if (initialized) return; michael@0: michael@0: jclass lObjectClass = jenv->FindClass("java/lang/Object"); michael@0: jclass lStringClass = jenv->FindClass("java/lang/String"); michael@0: jclass lByteBufferClass = jenv->FindClass("java/nio/ByteBuffer"); michael@0: jclass lCursorClass = jenv->FindClass("org/mozilla/gecko/sqlite/MatrixBlobCursor"); michael@0: michael@0: if (lStringClass == nullptr michael@0: || lObjectClass == nullptr michael@0: || lByteBufferClass == nullptr michael@0: || lCursorClass == nullptr) { michael@0: throwSqliteException(jenv, "FindClass error"); michael@0: return; michael@0: } michael@0: michael@0: // Those are only local references. Make them global so they work michael@0: // across calls and threads. michael@0: objectClass = (jclass)jenv->NewGlobalRef(lObjectClass); michael@0: stringClass = (jclass)jenv->NewGlobalRef(lStringClass); michael@0: byteBufferClass = (jclass)jenv->NewGlobalRef(lByteBufferClass); michael@0: cursorClass = (jclass)jenv->NewGlobalRef(lCursorClass); michael@0: michael@0: if (stringClass == nullptr || objectClass == nullptr michael@0: || byteBufferClass == nullptr michael@0: || cursorClass == nullptr) { michael@0: throwSqliteException(jenv, "NewGlobalRef error"); michael@0: return; michael@0: } michael@0: michael@0: // public static ByteBuffer allocateDirect(int capacity) michael@0: jByteBufferAllocateDirect = michael@0: jenv->GetStaticMethodID(byteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"); michael@0: // new MatrixBlobCursor(String []) michael@0: jCursorConstructor = michael@0: jenv->GetMethodID(cursorClass, "", "([Ljava/lang/String;)V"); michael@0: // public void addRow (Object[] columnValues) michael@0: jCursorAddRow = michael@0: jenv->GetMethodID(cursorClass, "addRow", "([Ljava/lang/Object;)V"); michael@0: michael@0: if (jByteBufferAllocateDirect == nullptr michael@0: || jCursorConstructor == nullptr michael@0: || jCursorAddRow == nullptr) { michael@0: throwSqliteException(jenv, "GetMethodId error"); michael@0: return; michael@0: } michael@0: michael@0: initialized = true; michael@0: } michael@0: michael@0: extern "C" NS_EXPORT jobject JNICALL michael@0: Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, michael@0: jstring jDb, michael@0: jstring jQuery, michael@0: jobjectArray jParams, michael@0: jlongArray jQueryRes) michael@0: { michael@0: JNI_Setup(jenv); michael@0: michael@0: int rc; michael@0: jobject jCursor = nullptr; michael@0: const char* dbPath; michael@0: sqlite3 *db; michael@0: michael@0: dbPath = jenv->GetStringUTFChars(jDb, nullptr); michael@0: rc = f_sqlite3_open(dbPath, &db); michael@0: jenv->ReleaseStringUTFChars(jDb, dbPath); michael@0: if (rc != SQLITE_OK) { michael@0: throwSqliteException(jenv, michael@0: "Can't open database: %s", f_sqlite3_errmsg(db)); michael@0: f_sqlite3_close(db); // close db even if open failed michael@0: return nullptr; michael@0: } michael@0: jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); michael@0: f_sqlite3_close(db); michael@0: return jCursor; michael@0: } michael@0: michael@0: extern "C" NS_EXPORT jobject JNICALL michael@0: Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCallWithDb(JNIEnv* jenv, jclass, michael@0: jlong jDb, michael@0: jstring jQuery, michael@0: jobjectArray jParams, michael@0: jlongArray jQueryRes) michael@0: { michael@0: JNI_Setup(jenv); michael@0: michael@0: jobject jCursor = nullptr; michael@0: sqlite3 *db = (sqlite3*)jDb; michael@0: jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); michael@0: return jCursor; michael@0: } michael@0: michael@0: extern "C" NS_EXPORT jlong JNICALL michael@0: Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass, michael@0: jstring jDb) michael@0: { michael@0: JNI_Setup(jenv); michael@0: michael@0: int rc; michael@0: const char* dbPath; michael@0: sqlite3 *db; michael@0: michael@0: dbPath = jenv->GetStringUTFChars(jDb, nullptr); michael@0: rc = f_sqlite3_open(dbPath, &db); michael@0: jenv->ReleaseStringUTFChars(jDb, dbPath); michael@0: if (rc != SQLITE_OK) { michael@0: throwSqliteException(jenv, michael@0: "Can't open database: %s", f_sqlite3_errmsg(db)); michael@0: f_sqlite3_close(db); // close db even if open failed michael@0: return 0; michael@0: } michael@0: return (jlong)db; michael@0: } michael@0: michael@0: extern "C" NS_EXPORT void JNICALL michael@0: Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass, michael@0: jlong jDb) michael@0: { michael@0: JNI_Setup(jenv); michael@0: michael@0: sqlite3 *db = (sqlite3*)jDb; michael@0: f_sqlite3_close(db); michael@0: } michael@0: michael@0: static jobject michael@0: sqliteInternalCall(JNIEnv* jenv, michael@0: sqlite3 *db, michael@0: jstring jQuery, michael@0: jobjectArray jParams, michael@0: jlongArray jQueryRes) michael@0: { michael@0: JNI_Setup(jenv); michael@0: michael@0: jobject jCursor = nullptr; michael@0: jsize numPars = 0; michael@0: michael@0: const char *pzTail; michael@0: sqlite3_stmt *ppStmt; michael@0: int rc; michael@0: michael@0: const char* queryStr; michael@0: queryStr = jenv->GetStringUTFChars(jQuery, nullptr); michael@0: michael@0: rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail); michael@0: if (rc != SQLITE_OK || ppStmt == nullptr) { michael@0: throwSqliteException(jenv, michael@0: "Can't prepare statement: %s", f_sqlite3_errmsg(db)); michael@0: return nullptr; michael@0: } michael@0: jenv->ReleaseStringUTFChars(jQuery, queryStr); michael@0: michael@0: // Check if number of parameters matches michael@0: if (jParams != nullptr) { michael@0: numPars = jenv->GetArrayLength(jParams); michael@0: } michael@0: int sqlNumPars; michael@0: sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt); michael@0: if (numPars != sqlNumPars) { michael@0: throwSqliteException(jenv, michael@0: "Passed parameter count (%d) " michael@0: "doesn't match SQL parameter count (%d)", michael@0: numPars, sqlNumPars); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (jParams != nullptr) { michael@0: // Bind parameters, if any michael@0: if (numPars > 0) { michael@0: for (int i = 0; i < numPars; i++) { michael@0: jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i); michael@0: // IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf michael@0: // should be OK. michael@0: jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass); michael@0: if (isString != JNI_TRUE) { michael@0: throwSqliteException(jenv, michael@0: "Parameter is not of String type"); michael@0: return nullptr; michael@0: } michael@0: jstring jStringParam = (jstring)jObjectParam; michael@0: const char* paramStr = jenv->GetStringUTFChars(jStringParam, nullptr); michael@0: // SQLite parameters index from 1. michael@0: rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT); michael@0: jenv->ReleaseStringUTFChars(jStringParam, paramStr); michael@0: if (rc != SQLITE_OK) { michael@0: throwSqliteException(jenv, "Error binding query parameter"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Execute the query and step through the results michael@0: rc = f_sqlite3_step(ppStmt); michael@0: if (rc != SQLITE_ROW && rc != SQLITE_DONE) { michael@0: throwSqliteException(jenv, michael@0: "Can't step statement: (%d) %s", rc, f_sqlite3_errmsg(db)); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the column count and names michael@0: int cols; michael@0: cols = f_sqlite3_column_count(ppStmt); michael@0: michael@0: { michael@0: // Allocate a String[cols] michael@0: jobjectArray jStringArray = jenv->NewObjectArray(cols, michael@0: stringClass, michael@0: nullptr); michael@0: if (jStringArray == nullptr) { michael@0: throwSqliteException(jenv, "Can't allocate String[]"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Assign column names to the String[] michael@0: for (int i = 0; i < cols; i++) { michael@0: const char* colName = f_sqlite3_column_name(ppStmt, i); michael@0: jstring jStr = jenv->NewStringUTF(colName); michael@0: jenv->SetObjectArrayElement(jStringArray, i, jStr); michael@0: } michael@0: michael@0: // Construct the MatrixCursor(String[]) with given column names michael@0: jCursor = jenv->NewObject(cursorClass, michael@0: jCursorConstructor, michael@0: jStringArray); michael@0: if (jCursor == nullptr) { michael@0: throwSqliteException(jenv, "Can't allocate MatrixBlobCursor"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: // Return the id and number of changed rows in jQueryRes michael@0: { michael@0: jlong id = f_sqlite3_last_insert_rowid(db); michael@0: jenv->SetLongArrayRegion(jQueryRes, 0, 1, &id); michael@0: michael@0: jlong changed = f_sqlite3_changes(db); michael@0: jenv->SetLongArrayRegion(jQueryRes, 1, 1, &changed); michael@0: } michael@0: michael@0: // For each row, add an Object[] to the passed ArrayList, michael@0: // with that containing either String or ByteArray objects michael@0: // containing the columns michael@0: while (rc != SQLITE_DONE) { michael@0: // Process row michael@0: // Construct Object[] michael@0: jobjectArray jRow = jenv->NewObjectArray(cols, michael@0: objectClass, michael@0: nullptr); michael@0: if (jRow == nullptr) { michael@0: throwSqliteException(jenv, "Can't allocate jRow Object[]"); michael@0: return nullptr; michael@0: } michael@0: michael@0: for (int i = 0; i < cols; i++) { michael@0: int colType = f_sqlite3_column_type(ppStmt, i); michael@0: if (colType == SQLITE_BLOB) { michael@0: // Treat as blob michael@0: const void* blob = f_sqlite3_column_blob(ppStmt, i); michael@0: int colLen = f_sqlite3_column_bytes(ppStmt, i); michael@0: michael@0: // Construct ByteBuffer of correct size michael@0: jobject jByteBuffer = michael@0: jenv->CallStaticObjectMethod(byteBufferClass, michael@0: jByteBufferAllocateDirect, michael@0: colLen); michael@0: if (jByteBuffer == nullptr) { michael@0: throwSqliteException(jenv, michael@0: "Failure calling ByteBuffer.allocateDirect"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get its backing array michael@0: void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer); michael@0: if (bufferArray == nullptr) { michael@0: throwSqliteException(jenv, michael@0: "Failure calling GetDirectBufferAddress"); michael@0: return nullptr; michael@0: } michael@0: memcpy(bufferArray, blob, colLen); michael@0: michael@0: jenv->SetObjectArrayElement(jRow, i, jByteBuffer); michael@0: jenv->DeleteLocalRef(jByteBuffer); michael@0: } else if (colType == SQLITE_NULL) { michael@0: jenv->SetObjectArrayElement(jRow, i, nullptr); michael@0: } else { michael@0: // Treat everything else as text michael@0: const char* txt = (const char*)f_sqlite3_column_text(ppStmt, i); michael@0: jstring jStr = jenv->NewStringUTF(txt); michael@0: jenv->SetObjectArrayElement(jRow, i, jStr); michael@0: jenv->DeleteLocalRef(jStr); michael@0: } michael@0: } michael@0: michael@0: // Append Object[] to Cursor michael@0: jenv->CallVoidMethod(jCursor, jCursorAddRow, jRow); michael@0: michael@0: // Clean up michael@0: jenv->DeleteLocalRef(jRow); michael@0: michael@0: // Get next row michael@0: rc = f_sqlite3_step(ppStmt); michael@0: // Real error? michael@0: if (rc != SQLITE_ROW && rc != SQLITE_DONE) { michael@0: throwSqliteException(jenv, michael@0: "Can't re-step statement:(%d) %s", rc, f_sqlite3_errmsg(db)); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: rc = f_sqlite3_finalize(ppStmt); michael@0: if (rc != SQLITE_OK) { michael@0: throwSqliteException(jenv, michael@0: "Can't finalize statement: %s", f_sqlite3_errmsg(db)); michael@0: return nullptr; michael@0: } michael@0: michael@0: return jCursor; michael@0: }