1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#undef LOG_TAG
18#define LOG_TAG "CursorWindow"
19#define LOG_NDEBUG 0
20
21#include <inttypes.h>
22#include <jni.h>
23#include <nativehelper/JNIHelp.h>
24#include <android_runtime/AndroidRuntime.h>
25
26#include <utils/Log.h>
27#include <utils/String8.h>
28#include <utils/String16.h>
29#include <utils/Unicode.h>
30
31#include <stdio.h>
32#include <string.h>
33#include <unistd.h>
34#include <sys/types.h>
35#include <dirent.h>
36
37#undef LOG_NDEBUG
38#define LOG_NDEBUG 1
39
40#include <androidfw/CursorWindow.h>
41#include "android_os_Parcel.h"
42#include "android_util_Binder.h"
43#include "android_database_SQLiteCommon.h"
44
45#include "core_jni_helpers.h"
46
47namespace android {
48
49static struct {
50    jfieldID data;
51    jfieldID sizeCopied;
52} gCharArrayBufferClassInfo;
53
54static jstring gEmptyString;
55
56static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
57    String8 msg;
58    msg.appendFormat("Couldn't read row %d, col %d from CursorWindow.  "
59            "Make sure the Cursor is initialized correctly before accessing data from it.",
60            row, column);
61    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
62}
63
64static void throwUnknownTypeException(JNIEnv * env, jint type) {
65    String8 msg;
66    msg.appendFormat("UNKNOWN type %d", type);
67    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
68}
69
70static int getFdCount() {
71    char fdpath[PATH_MAX];
72    int count = 0;
73    snprintf(fdpath, PATH_MAX, "/proc/%d/fd", getpid());
74    DIR *dir = opendir(fdpath);
75    if (dir != NULL) {
76        struct dirent *dirent;
77        while ((dirent = readdir(dir))) {
78            count++;
79        }
80        count -= 2; // discount "." and ".."
81        closedir(dir);
82    }
83    return count;
84}
85
86static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
87    String8 name;
88    const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
89    name.setTo(nameStr);
90    env->ReleaseStringUTFChars(nameObj, nameStr);
91
92    CursorWindow* window;
93    status_t status = CursorWindow::create(name, cursorWindowSize, &window);
94    if (status || !window) {
95        ALOGE("Could not allocate CursorWindow '%s' of size %d due to error %d.",
96                name.string(), cursorWindowSize, status);
97        return 0;
98    }
99
100    LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
101    return reinterpret_cast<jlong>(window);
102}
103
104static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
105    Parcel* parcel = parcelForJavaObject(env, parcelObj);
106
107    CursorWindow* window;
108    status_t status = CursorWindow::createFromParcel(parcel, &window);
109    if (status || !window) {
110        ALOGE("Could not create CursorWindow from Parcel due to error %d, process fd count=%d",
111                status, getFdCount());
112        return 0;
113    }
114
115    LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
116            window->getNumRows(), window->getNumColumns(), window);
117    return reinterpret_cast<jlong>(window);
118}
119
120static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
121    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
122    if (window) {
123        LOG_WINDOW("Closing window %p", window);
124        delete window;
125    }
126}
127
128static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
129    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
130    return env->NewStringUTF(window->name().string());
131}
132
133static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
134        jobject parcelObj) {
135    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
136    Parcel* parcel = parcelForJavaObject(env, parcelObj);
137
138    status_t status = window->writeToParcel(parcel);
139    if (status) {
140        String8 msg;
141        msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
142        jniThrowRuntimeException(env, msg.string());
143    }
144}
145
146static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
147    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
148    LOG_WINDOW("Clearing window %p", window);
149    status_t status = window->clear();
150    if (status) {
151        LOG_WINDOW("Could not clear window. error=%d", status);
152    }
153}
154
155static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
156    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
157    return window->getNumRows();
158}
159
160static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
161        jint columnNum) {
162    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
163    status_t status = window->setNumColumns(columnNum);
164    return status == OK;
165}
166
167static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
168    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
169    status_t status = window->allocRow();
170    return status == OK;
171}
172
173static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
174    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
175    window->freeLastRow();
176}
177
178static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr,
179        jint row, jint column) {
180    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
181    LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
182
183    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
184    if (!fieldSlot) {
185        // FIXME: This is really broken but we have CTS tests that depend
186        // on this legacy behavior.
187        //throwExceptionWithRowCol(env, row, column);
188        return CursorWindow::FIELD_TYPE_NULL;
189    }
190    return window->getFieldSlotType(fieldSlot);
191}
192
193static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
194        jint row, jint column) {
195    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
196    LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
197
198    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
199    if (!fieldSlot) {
200        throwExceptionWithRowCol(env, row, column);
201        return NULL;
202    }
203
204    int32_t type = window->getFieldSlotType(fieldSlot);
205    if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
206        size_t size;
207        const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
208        if (!value) {
209            throw_sqlite3_exception(env, "Native could not read blob slot");
210            return NULL;
211        }
212        jbyteArray byteArray = env->NewByteArray(size);
213        if (!byteArray) {
214            env->ExceptionClear();
215            throw_sqlite3_exception(env, "Native could not create new byte[]");
216            return NULL;
217        }
218        env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(value));
219        return byteArray;
220    } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
221        throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
222    } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
223        throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
224    } else if (type == CursorWindow::FIELD_TYPE_NULL) {
225        // do nothing
226    } else {
227        throwUnknownTypeException(env, type);
228    }
229    return NULL;
230}
231
232static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
233        jint row, jint column) {
234    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
235    LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
236
237    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
238    if (!fieldSlot) {
239        throwExceptionWithRowCol(env, row, column);
240        return NULL;
241    }
242
243    int32_t type = window->getFieldSlotType(fieldSlot);
244    if (type == CursorWindow::FIELD_TYPE_STRING) {
245        size_t sizeIncludingNull;
246        const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
247        if (!value) {
248            throw_sqlite3_exception(env, "Native could not read string slot");
249            return NULL;
250        }
251        if (sizeIncludingNull <= 1) {
252            return gEmptyString;
253        }
254        // Convert to UTF-16 here instead of calling NewStringUTF.  NewStringUTF
255        // doesn't like UTF-8 strings with high codepoints.  It actually expects
256        // Modified UTF-8 with encoded surrogate pairs.
257        String16 utf16(value, sizeIncludingNull - 1);
258        return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
259    } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
260        int64_t value = window->getFieldSlotValueLong(fieldSlot);
261        char buf[32];
262        snprintf(buf, sizeof(buf), "%" PRId64, value);
263        return env->NewStringUTF(buf);
264    } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
265        double value = window->getFieldSlotValueDouble(fieldSlot);
266        char buf[32];
267        snprintf(buf, sizeof(buf), "%g", value);
268        return env->NewStringUTF(buf);
269    } else if (type == CursorWindow::FIELD_TYPE_NULL) {
270        return NULL;
271    } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
272        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
273        return NULL;
274    } else {
275        throwUnknownTypeException(env, type);
276        return NULL;
277    }
278}
279
280static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
281    jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
282            gCharArrayBufferClassInfo.data));
283    if (dataObj && size) {
284        jsize capacity = env->GetArrayLength(dataObj);
285        if (size_t(capacity) < size) {
286            env->DeleteLocalRef(dataObj);
287            dataObj = NULL;
288        }
289    }
290    if (!dataObj) {
291        jsize capacity = size;
292        if (capacity < 64) {
293            capacity = 64;
294        }
295        dataObj = env->NewCharArray(capacity); // might throw OOM
296        if (dataObj) {
297            env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
298        }
299    }
300    return dataObj;
301}
302
303static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
304        const char* str, size_t len) {
305    ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
306    if (size < 0) {
307        size = 0; // invalid UTF8 string
308    }
309    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
310    if (dataObj) {
311        if (size) {
312            jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
313            utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
314                    reinterpret_cast<char16_t*>(data), (size_t) size);
315            env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
316        }
317        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
318    }
319}
320
321static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
322    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
323    if (dataObj) {
324        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
325    }
326}
327
328static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jlong windowPtr,
329        jint row, jint column, jobject bufferObj) {
330    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
331    LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
332
333    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
334    if (!fieldSlot) {
335        throwExceptionWithRowCol(env, row, column);
336        return;
337    }
338
339    int32_t type = window->getFieldSlotType(fieldSlot);
340    if (type == CursorWindow::FIELD_TYPE_STRING) {
341        size_t sizeIncludingNull;
342        const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
343        if (sizeIncludingNull > 1) {
344            fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
345        } else {
346            clearCharArrayBuffer(env, bufferObj);
347        }
348    } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
349        int64_t value = window->getFieldSlotValueLong(fieldSlot);
350        char buf[32];
351        snprintf(buf, sizeof(buf), "%" PRId64, value);
352        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
353    } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
354        double value = window->getFieldSlotValueDouble(fieldSlot);
355        char buf[32];
356        snprintf(buf, sizeof(buf), "%g", value);
357        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
358    } else if (type == CursorWindow::FIELD_TYPE_NULL) {
359        clearCharArrayBuffer(env, bufferObj);
360    } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
361        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
362    } else {
363        throwUnknownTypeException(env, type);
364    }
365}
366
367static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr,
368        jint row, jint column) {
369    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
370    LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
371
372    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
373    if (!fieldSlot) {
374        throwExceptionWithRowCol(env, row, column);
375        return 0;
376    }
377
378    int32_t type = window->getFieldSlotType(fieldSlot);
379    if (type == CursorWindow::FIELD_TYPE_INTEGER) {
380        return window->getFieldSlotValueLong(fieldSlot);
381    } else if (type == CursorWindow::FIELD_TYPE_STRING) {
382        size_t sizeIncludingNull;
383        const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
384        return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
385    } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
386        return jlong(window->getFieldSlotValueDouble(fieldSlot));
387    } else if (type == CursorWindow::FIELD_TYPE_NULL) {
388        return 0;
389    } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
390        throw_sqlite3_exception(env, "Unable to convert BLOB to long");
391        return 0;
392    } else {
393        throwUnknownTypeException(env, type);
394        return 0;
395    }
396}
397
398static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
399        jint row, jint column) {
400    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
401    LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
402
403    CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
404    if (!fieldSlot) {
405        throwExceptionWithRowCol(env, row, column);
406        return 0.0;
407    }
408
409    int32_t type = window->getFieldSlotType(fieldSlot);
410    if (type == CursorWindow::FIELD_TYPE_FLOAT) {
411        return window->getFieldSlotValueDouble(fieldSlot);
412    } else if (type == CursorWindow::FIELD_TYPE_STRING) {
413        size_t sizeIncludingNull;
414        const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
415        return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
416    } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
417        return jdouble(window->getFieldSlotValueLong(fieldSlot));
418    } else if (type == CursorWindow::FIELD_TYPE_NULL) {
419        return 0.0;
420    } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
421        throw_sqlite3_exception(env, "Unable to convert BLOB to double");
422        return 0.0;
423    } else {
424        throwUnknownTypeException(env, type);
425        return 0.0;
426    }
427}
428
429static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
430        jbyteArray valueObj, jint row, jint column) {
431    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
432    jsize len = env->GetArrayLength(valueObj);
433
434    void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
435    status_t status = window->putBlob(row, column, value, len);
436    env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
437
438    if (status) {
439        LOG_WINDOW("Failed to put blob. error=%d", status);
440        return false;
441    }
442
443    LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
444    return true;
445}
446
447static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
448        jstring valueObj, jint row, jint column) {
449    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
450
451    size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
452    const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
453    if (!valueStr) {
454        LOG_WINDOW("value can't be transferred to UTFChars");
455        return false;
456    }
457    status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
458    env->ReleaseStringUTFChars(valueObj, valueStr);
459
460    if (status) {
461        LOG_WINDOW("Failed to put string. error=%d", status);
462        return false;
463    }
464
465    LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
466    return true;
467}
468
469static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
470        jlong value, jint row, jint column) {
471    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
472    status_t status = window->putLong(row, column, value);
473
474    if (status) {
475        LOG_WINDOW("Failed to put long. error=%d", status);
476        return false;
477    }
478
479    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
480    return true;
481}
482
483static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
484        jdouble value, jint row, jint column) {
485    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
486    status_t status = window->putDouble(row, column, value);
487
488    if (status) {
489        LOG_WINDOW("Failed to put double. error=%d", status);
490        return false;
491    }
492
493    LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
494    return true;
495}
496
497static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
498        jint row, jint column) {
499    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
500    status_t status = window->putNull(row, column);
501
502    if (status) {
503        LOG_WINDOW("Failed to put null. error=%d", status);
504        return false;
505    }
506
507    LOG_WINDOW("%d,%d is NULL", row, column);
508    return true;
509}
510
511static const JNINativeMethod sMethods[] =
512{
513    /* name, signature, funcPtr */
514    { "nativeCreate", "(Ljava/lang/String;I)J",
515            (void*)nativeCreate },
516    { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
517            (void*)nativeCreateFromParcel },
518    { "nativeDispose", "(J)V",
519            (void*)nativeDispose },
520    { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
521            (void*)nativeWriteToParcel },
522
523    { "nativeGetName", "(J)Ljava/lang/String;",
524            (void*)nativeGetName },
525    { "nativeGetBlob", "(JII)[B",
526            (void*)nativeGetBlob },
527    { "nativeGetString", "(JII)Ljava/lang/String;",
528            (void*)nativeGetString },
529    { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
530            (void*)nativeCopyStringToBuffer },
531    { "nativePutBlob", "(J[BII)Z",
532            (void*)nativePutBlob },
533    { "nativePutString", "(JLjava/lang/String;II)Z",
534            (void*)nativePutString },
535
536    // ------- @FastNative below here ----------------------
537    { "nativeClear", "(J)V",
538            (void*)nativeClear },
539    { "nativeGetNumRows", "(J)I",
540            (void*)nativeGetNumRows },
541    { "nativeSetNumColumns", "(JI)Z",
542            (void*)nativeSetNumColumns },
543    { "nativeAllocRow", "(J)Z",
544            (void*)nativeAllocRow },
545    { "nativeFreeLastRow", "(J)V",
546            (void*)nativeFreeLastRow },
547    { "nativeGetType", "(JII)I",
548            (void*)nativeGetType },
549    { "nativeGetLong", "(JII)J",
550            (void*)nativeGetLong },
551    { "nativeGetDouble", "(JII)D",
552            (void*)nativeGetDouble },
553
554    { "nativePutLong", "(JJII)Z",
555            (void*)nativePutLong },
556    { "nativePutDouble", "(JDII)Z",
557            (void*)nativePutDouble },
558    { "nativePutNull", "(JII)Z",
559            (void*)nativePutNull },
560};
561
562int register_android_database_CursorWindow(JNIEnv* env)
563{
564    jclass clazz = FindClassOrDie(env, "android/database/CharArrayBuffer");
565
566    gCharArrayBufferClassInfo.data = GetFieldIDOrDie(env, clazz, "data", "[C");
567    gCharArrayBufferClassInfo.sizeCopied = GetFieldIDOrDie(env, clazz, "sizeCopied", "I");
568
569    gEmptyString = MakeGlobalRefOrDie(env, env->NewStringUTF(""));
570
571    return RegisterMethodsOrDie(env, "android/database/CursorWindow", sMethods, NELEM(sMethods));
572}
573
574} // namespace android
575