blob: 391c5d56b212a804486430cf2917426b0b75d8a3 [file] [log] [blame]
/*
* 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.
*/
/*
* This file is compiled into a single SO file, which we load at the very first.
* We can do process-wide initialization here.
* Please be aware that all symbols defined in this SO file will be reloaded
* as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY
* want to expose and override globally.
*/
#include <dlfcn.h>
#include <fcntl.h>
#include <set>
#include "jni_helper.h"
// Implement a rudimentary system properties data store
#define PROP_VALUE_MAX 92
namespace {
struct prop_info {
std::string key;
mutable std::string value;
mutable uint32_t serial;
prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
};
struct prop_info_cmp {
using is_transparent = void;
bool operator()(const prop_info& lhs, const prop_info& rhs) {
return lhs.key < rhs.key;
}
bool operator()(std::string_view lhs, const prop_info& rhs) {
return lhs < rhs.key;
}
bool operator()(const prop_info& lhs, std::string_view rhs) {
return lhs.key < rhs;
}
};
} // namespace
static auto& g_properties_lock = *new std::mutex;
static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
static bool property_set(const char* key, const char* value) {
if (key == nullptr || *key == '\0') return false;
if (value == nullptr) value = "";
bool read_only = !strncmp(key, "ro.", 3);
if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
std::lock_guard lock(g_properties_lock);
auto [it, success] = g_properties.emplace(key, value);
if (read_only) return success;
if (!success) {
it->value = value;
++it->serial;
}
return true;
}
template <typename Func>
static void property_get(const char* key, Func callback) {
std::lock_guard lock(g_properties_lock);
auto it = g_properties.find(key);
if (it != g_properties.end()) {
callback(*it);
}
}
// Redefine the __system_property_XXX functions here so we can perform
// logging and access checks for all sysprops in native code.
static void check_system_property_access(const char* key, bool write);
extern "C" {
int __system_property_set(const char* key, const char* value) {
check_system_property_access(key, true);
return property_set(key, value) ? 0 : -1;
}
int __system_property_get(const char* key, char* value) {
check_system_property_access(key, false);
*value = '\0';
property_get(key, [&](const prop_info& info) {
snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
});
return strlen(value);
}
const prop_info* __system_property_find(const char* key) {
check_system_property_access(key, false);
const prop_info* pi = nullptr;
property_get(key, [&](const prop_info& info) { pi = &info; });
return pi;
}
void __system_property_read_callback(const prop_info* pi,
void (*callback)(void*, const char*, const char*, uint32_t),
void* cookie) {
std::lock_guard lock(g_properties_lock);
callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
}
} // extern "C"
// ---- JNI ----
static JavaVM* gVM = nullptr;
static jclass gRunnerState = nullptr;
static jmethodID gCheckSystemPropertyAccess;
static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
ScopedUtfChars path(env, javaPath);
// Force reload ourselves as global
dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
}
// Call back into Java code to check property access
static void check_system_property_access(const char* key, bool write) {
if (gVM != nullptr && gRunnerState != nullptr) {
JNIEnv* env;
if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
ALOGV("%s access to system property '%s'", write ? "Write" : "Read", key);
env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
return;
}
}
// Not on JVM thread, abort
LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
}
static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
ScopedUtfChars key(env, javaKey);
jstring value = nullptr;
property_get(key.c_str(),
[&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
return value;
}
static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
ScopedUtfChars key(env, javaKey);
ScopedUtfChars value(env, javaValue);
return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
}
static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
std::lock_guard lock(g_properties_lock);
if (javaKey == nullptr) {
g_properties.clear();
return JNI_TRUE;
} else {
ScopedUtfChars key(env, javaKey);
auto it = g_properties.find(key);
if (it != g_properties.end()) {
g_properties.erase(it);
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
}
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
if (ravenwoodLogOut == NULL) {
return;
}
ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
// Redirect stdin / stdout to /dev/tty.
int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND);
if (ttyFd == -1) {
ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
strerror(errno));
return;
}
dup2(ttyFd, 1);
dup2(ttyFd, 2);
}
static const JNINativeMethod sMethods[] = {
{"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
{"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
{"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
{"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
ALOGV("%s: JNI_OnLoad", __FILE__);
maybeRedirectLog();
JNIEnv* env = GetJNIEnvOrDie(vm);
gVM = vm;
// Fetch several references for future use
gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
gCheckSystemPropertyAccess =
GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
"(Ljava/lang/String;Z)V");
// Expose raw property methods as JNI methods
jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
if (res < 0) return -1;
return JNI_VERSION_1_4;
}