Also returning nullptr instead of "" from a function returning std::string in the zygote. Change-Id: I6229603e8684d7e34d8536611d0852f071f52cd4
300 lines
9.6 KiB
C++
300 lines
9.6 KiB
C++
/*
|
|
* 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 <fstream>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
|
|
#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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the PPID of child_pid using /proc/N/stat. The 4th field is the PPID.
|
|
// Also returns child_pid's process name (2nd field).
|
|
static pid_t getppid_of(pid_t child_pid, std::string& out_process_name) {
|
|
if (child_pid < 0) {
|
|
return -1;
|
|
}
|
|
std::string stat_file = "/proc/" + std::to_string(child_pid) + "/stat";
|
|
std::ifstream stat_stream(stat_file);
|
|
if (!stat_stream.is_open()) {
|
|
ALOGW("Unable to open '%s': %s", stat_file.c_str(), strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
std::string field;
|
|
int field_count = 0;
|
|
while (std::getline(stat_stream, field, ' ')) {
|
|
if (++field_count == 4) {
|
|
return atoi(field.c_str());
|
|
}
|
|
if (field_count == 2) {
|
|
out_process_name = field;
|
|
}
|
|
}
|
|
ALOGW("Unexpected format in '%s'", stat_file.c_str());
|
|
return -1;
|
|
}
|
|
|
|
// Find atest's PID. Climb up the process tree, and find "atest-py3".
|
|
static pid_t find_atest_pid() {
|
|
auto ret = getpid(); // self (isolation runner process)
|
|
|
|
while (ret != -1) {
|
|
std::string proc;
|
|
ret = getppid_of(ret, proc);
|
|
if (proc == "(atest-py3)") {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// If $RAVENWOOD_LOG_OUT is set, redirect stdout/err to this file.
|
|
// Originally it was added to allow to monitor log in realtime, with
|
|
// RAVENWOOD_LOG_OUT=$(tty) atest...
|
|
//
|
|
// As a special case, if $RAVENWOOD_LOG_OUT is set to "-", we try to find
|
|
// atest's process and send the output to its stdout. It's sort of hacky, but
|
|
// this allows shell redirection to work on Ravenwood output too,
|
|
// so e.g. `atest ... |tee atest.log` would work on Ravenwood's output.
|
|
// (which wouldn't work with `RAVENWOOD_LOG_OUT=$(tty)`).
|
|
//
|
|
// Otherwise -- if $RAVENWOOD_LOG_OUT isn't set -- atest/tradefed just writes
|
|
// the test's output to its own log file.
|
|
static void maybeRedirectLog() {
|
|
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
|
|
if (ravenwoodLogOut == NULL || *ravenwoodLogOut == '\0') {
|
|
return;
|
|
}
|
|
std::string path;
|
|
if (strcmp("-", ravenwoodLogOut) == 0) {
|
|
pid_t ppid = find_atest_pid();
|
|
if (ppid < 0) {
|
|
ALOGI("RAVENWOOD_LOG_OUT set to '-', but unable to find atest's PID");
|
|
return;
|
|
}
|
|
path = std::format("/proc/{}/fd/1", ppid);
|
|
} else {
|
|
path = ravenwoodLogOut;
|
|
}
|
|
ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to '%s'", path.c_str());
|
|
|
|
// Redirect stdin / stdout to /dev/tty.
|
|
int ttyFd = open(path.c_str(), O_WRONLY | O_APPEND);
|
|
if (ttyFd == -1) {
|
|
ALOGW("$RAVENWOOD_LOG_OUT is set, but failed to open '%s': %s ", path.c_str(),
|
|
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;
|
|
}
|