Ensure local settings caches are not stale

We used the system proterties as a shared memory mechanism
to propagate information to local settings caches when the
content has changed and the cache should be cleared. The
system properties are unfortunately updated asynchronously
leading to cases where clients may read stale data.

This change adds a simple int array data structure backed
by shared memory which guarantees individual values are
atomically read and updated without memory tear. Multi-
index opearations are not synchronized between each other.

The settings provider is using the new data structure to
propagate the settings generation which drives when caches
are purged.

We have a single memory array keeping the generation for
different settings tables per user. Since memory array is
not a compact data structure and the user space exceeds
the memory array size we use an in-memory map from keys
to indices in the memory array where the generation id of
a key is stored. A key is derived by the setting type in
the 4 most significant bits and the user id in the 28 least
significant bits.

The mapping from a key to an index is cleared if the user is
removed and the corresponding index in the memory arry is
reset to make it available for other users. The size of the
memory array is derived from the max user count that can be
created at the same time.

bug:18826179

Change-Id: I64009cc5105309ef9aa83aba90b82afc8ad8c659
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
new file mode 100644
index 0000000..dbe8ed3
--- /dev/null
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+
+#include "core_jni_helpers.h"
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+namespace android {
+
+static jint android_util_MemoryIntArray_create(JNIEnv* env, jobject clazz, jstring name,
+        jint size)
+{
+    if (name == NULL) {
+        jniThrowException(env, "java/io/IOException", "bad name");
+        return -1;
+    }
+
+    if (size <= 0) {
+        jniThrowException(env, "java/io/IOException", "bad size");
+        return -1;
+    }
+
+    const char* nameStr = env->GetStringUTFChars(name, NULL);
+    const int ashmemSize = sizeof(std::atomic_int) * size;
+    int fd = ashmem_create_region(nameStr, ashmemSize);
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "ashmem creation failed");
+        return -1;
+    }
+
+    if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+        jniThrowException(env, "java/io/IOException", "ashmem was purged");
+        return -1;
+    }
+
+    int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+    if (setProtResult < 0) {
+        jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
+        return -1;
+    }
+
+    return fd;
+}
+
+static jlong android_util_MemoryIntArray_open(JNIEnv* env, jobject clazz, jint fd,
+    jboolean owner, jboolean writable)
+{
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return -1;
+    }
+
+    int ashmemSize = ashmem_get_size_region(fd);
+    if (ashmemSize <= 0) {
+        jniThrowException(env, "java/io/IOException", "bad ashmem size");
+        return -1;
+    }
+
+    int protMode = (owner || writable) ? (PROT_READ | PROT_WRITE) : PROT_READ;
+    void* ashmemAddr = mmap(NULL, ashmemSize, protMode, MAP_SHARED, fd, 0);
+    if (ashmemAddr == MAP_FAILED) {
+        jniThrowException(env, "java/io/IOException", "cannot mmap ashmem");
+        return -1;
+    }
+
+    if (owner) {
+        int size = ashmemSize / sizeof(std::atomic_int);
+        new (ashmemAddr) std::atomic_int[size];
+    }
+
+    if (owner && !writable) {
+        int setProtResult = ashmem_set_prot_region(fd, PROT_READ);
+        if (setProtResult < 0) {
+            jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
+            return -1;
+        }
+    }
+
+    return reinterpret_cast<jlong>(ashmemAddr);
+}
+
+static void android_util_MemoryIntArray_close(JNIEnv* env, jobject clazz, jint fd,
+    jlong ashmemAddr, jboolean owner)
+{
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return;
+    }
+
+    int ashmemSize = ashmem_get_size_region(fd);
+    if (ashmemSize <= 0) {
+        jniThrowException(env, "java/io/IOException", "bad ashmem size");
+        return;
+    }
+
+    int unmapResult = munmap(reinterpret_cast<void *>(ashmemAddr), ashmemSize);
+    if (unmapResult < 0) {
+        jniThrowException(env, "java/io/IOException", "munmap failed");
+        return;
+    }
+
+    // We don't deallocate the atomic ints we created with placement new in the ashmem
+    // region as the kernel we reclaim all pages when the ashmem region is destroyed.
+    if (owner && (ashmem_unpin_region(fd, 0, 0) != ASHMEM_IS_UNPINNED)) {
+        jniThrowException(env, "java/io/IOException", "ashmem unpinning failed");
+        return;
+    }
+
+    close(fd);
+}
+
+static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
+        jint fd, jlong address, jint index, jboolean owner)
+{
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return -1;
+    }
+
+    bool unpin = false;
+
+    if (!owner) {
+        if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+            jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+            return -1;
+        }
+        unpin = true;
+    }
+
+    std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
+    const int result = value->load(std::memory_order_relaxed);
+
+    if (unpin) {
+        ashmem_unpin_region(fd, 0, 0);
+    }
+
+    return result;
+}
+
+static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
+        jint fd, jlong address, jint index, jint newValue, jboolean owner)
+{
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return;
+    }
+
+    bool unpin = false;
+
+    if (!owner) {
+        if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
+            jniThrowException(env, "java/io/IOException", "ashmem region was purged");
+            return;
+        }
+        unpin = true;
+    }
+
+    std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
+    value->store(newValue, std::memory_order_relaxed);
+
+    if (unpin) {
+        ashmem_unpin_region(fd, 0, 0);
+    }
+}
+
+static jint android_util_MemoryIntArray_size(JNIEnv* env, jobject clazz, jint fd) {
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return -1;
+    }
+
+    // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
+    // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
+    // should return ENOTTY for all other valid file descriptors
+    int ashmemSize = ashmem_get_size_region(fd);
+    if (ashmemSize < 0) {
+        if (errno == ENOTTY) {
+            // ENOTTY means that the ioctl does not apply to this object,
+            // i.e., it is not an ashmem region.
+            return -1;
+        }
+        // Some other error, throw exception
+        jniThrowIOException(env, errno);
+        return -1;
+    }
+    return ashmemSize / sizeof(std::atomic_int);
+}
+
+static const JNINativeMethod methods[] = {
+    {"nativeCreate",  "(Ljava/lang/String;I)I", (void*)android_util_MemoryIntArray_create},
+    {"nativeOpen",  "(IZZ)J", (void*)android_util_MemoryIntArray_open},
+    {"nativeClose", "(IJZ)V", (void*)android_util_MemoryIntArray_close},
+    {"nativeGet",  "(IJIZ)I", (void*)android_util_MemoryIntArray_get},
+    {"nativeSet", "(IJIIZ)V", (void*) android_util_MemoryIntArray_set},
+    {"nativeSize", "(I)I", (void*) android_util_MemoryIntArray_size},
+};
+
+int register_android_util_MemoryIntArray(JNIEnv* env)
+{
+    return RegisterMethodsOrDie(env, "android/util/MemoryIntArray", methods, NELEM(methods));
+}
+
+}