1 /*
2 * Copyright (C) 2024 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 /*
18 * This file is compiled into a single SO file, which we load at the very first.
19 * We can do process-wide initialization here.
20 * Please be aware that all symbols defined in this SO file will be reloaded
21 * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY
22 * want to expose and override globally.
23 */
24
25 #include <dlfcn.h>
26 #include <fcntl.h>
27
28 #include <set>
29 #include <fstream>
30 #include <iostream>
31 #include <string>
32 #include <cstdlib>
33
34 #include "jni_helper.h"
35
36 // Implement a rudimentary system properties data store
37
38 #define PROP_VALUE_MAX 92
39
40 namespace {
41
42 struct prop_info {
43 std::string key;
44 mutable std::string value;
45 mutable uint32_t serial;
46
prop_info__anonc2fd6f3d0111::prop_info47 prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
48 };
49
50 struct prop_info_cmp {
51 using is_transparent = void;
operator ()__anonc2fd6f3d0111::prop_info_cmp52 bool operator()(const prop_info& lhs, const prop_info& rhs) {
53 return lhs.key < rhs.key;
54 }
operator ()__anonc2fd6f3d0111::prop_info_cmp55 bool operator()(std::string_view lhs, const prop_info& rhs) {
56 return lhs < rhs.key;
57 }
operator ()__anonc2fd6f3d0111::prop_info_cmp58 bool operator()(const prop_info& lhs, std::string_view rhs) {
59 return lhs.key < rhs;
60 }
61 };
62
63 } // namespace
64
65 static auto& g_properties_lock = *new std::mutex;
66 static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
67
property_set(const char * key,const char * value)68 static bool property_set(const char* key, const char* value) {
69 if (key == nullptr || *key == '\0') return false;
70 if (value == nullptr) value = "";
71 bool read_only = !strncmp(key, "ro.", 3);
72 if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
73
74 std::lock_guard lock(g_properties_lock);
75 auto [it, success] = g_properties.emplace(key, value);
76 if (read_only) return success;
77 if (!success) {
78 it->value = value;
79 ++it->serial;
80 }
81 return true;
82 }
83
84 template <typename Func>
property_get(const char * key,Func callback)85 static void property_get(const char* key, Func callback) {
86 std::lock_guard lock(g_properties_lock);
87 auto it = g_properties.find(key);
88 if (it != g_properties.end()) {
89 callback(*it);
90 }
91 }
92
93 // Redefine the __system_property_XXX functions here so we can perform
94 // logging and access checks for all sysprops in native code.
95
96 static void check_system_property_access(const char* key, bool write);
97
98 extern "C" {
99
__system_property_set(const char * key,const char * value)100 int __system_property_set(const char* key, const char* value) {
101 check_system_property_access(key, true);
102 return property_set(key, value) ? 0 : -1;
103 }
104
__system_property_get(const char * key,char * value)105 int __system_property_get(const char* key, char* value) {
106 check_system_property_access(key, false);
107 *value = '\0';
108 property_get(key, [&](const prop_info& info) {
109 snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
110 });
111 return strlen(value);
112 }
113
__system_property_find(const char * key)114 const prop_info* __system_property_find(const char* key) {
115 check_system_property_access(key, false);
116 const prop_info* pi = nullptr;
117 property_get(key, [&](const prop_info& info) { pi = &info; });
118 return pi;
119 }
120
__system_property_read_callback(const prop_info * pi,void (* callback)(void *,const char *,const char *,uint32_t),void * cookie)121 void __system_property_read_callback(const prop_info* pi,
122 void (*callback)(void*, const char*, const char*, uint32_t),
123 void* cookie) {
124 std::lock_guard lock(g_properties_lock);
125 callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
126 }
127
128 } // extern "C"
129
130 // ---- JNI ----
131
132 static JavaVM* gVM = nullptr;
133 static jclass gRunnerState = nullptr;
134 static jmethodID gCheckSystemPropertyAccess;
135
reloadNativeLibrary(JNIEnv * env,jclass,jstring javaPath)136 static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
137 ScopedUtfChars path(env, javaPath);
138 // Force reload ourselves as global
139 dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
140 }
141
142 // Call back into Java code to check property access
check_system_property_access(const char * key,bool write)143 static void check_system_property_access(const char* key, bool write) {
144 if (gVM != nullptr && gRunnerState != nullptr) {
145 JNIEnv* env;
146 if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
147 ALOGV("%s access to system property '%s'", write ? "Write" : "Read", key);
148 env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
149 env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
150 return;
151 }
152 }
153 // Not on JVM thread, abort
154 LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
155 }
156
getSystemProperty(JNIEnv * env,jclass,jstring javaKey)157 static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
158 ScopedUtfChars key(env, javaKey);
159 jstring value = nullptr;
160 property_get(key.c_str(),
161 [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
162 return value;
163 }
164
setSystemProperty(JNIEnv * env,jclass,jstring javaKey,jstring javaValue)165 static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
166 ScopedUtfChars key(env, javaKey);
167 ScopedUtfChars value(env, javaValue);
168 return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
169 }
170
removeSystemProperty(JNIEnv * env,jclass,jstring javaKey)171 static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
172 std::lock_guard lock(g_properties_lock);
173
174 if (javaKey == nullptr) {
175 g_properties.clear();
176 return JNI_TRUE;
177 } else {
178 ScopedUtfChars key(env, javaKey);
179 auto it = g_properties.find(key);
180 if (it != g_properties.end()) {
181 g_properties.erase(it);
182 return JNI_TRUE;
183 } else {
184 return JNI_FALSE;
185 }
186 }
187 }
188
189 // Find the PPID of child_pid using /proc/N/stat. The 4th field is the PPID.
190 // Also returns child_pid's process name (2nd field).
getppid_of(pid_t child_pid,std::string & out_process_name)191 static pid_t getppid_of(pid_t child_pid, std::string& out_process_name) {
192 if (child_pid < 0) {
193 return -1;
194 }
195 std::string stat_file = "/proc/" + std::to_string(child_pid) + "/stat";
196 std::ifstream stat_stream(stat_file);
197 if (!stat_stream.is_open()) {
198 ALOGW("Unable to open '%s': %s", stat_file.c_str(), strerror(errno));
199 return -1;
200 }
201
202 std::string field;
203 int field_count = 0;
204 while (std::getline(stat_stream, field, ' ')) {
205 if (++field_count == 4) {
206 return atoi(field.c_str());
207 }
208 if (field_count == 2) {
209 out_process_name = field;
210 }
211 }
212 ALOGW("Unexpected format in '%s'", stat_file.c_str());
213 return -1;
214 }
215
216 // Find atest's PID. Climb up the process tree, and find "atest-py3".
find_atest_pid()217 static pid_t find_atest_pid() {
218 auto ret = getpid(); // self (isolation runner process)
219
220 while (ret != -1) {
221 std::string proc;
222 ret = getppid_of(ret, proc);
223 if (proc == "(atest-py3)") {
224 return ret;
225 }
226 }
227
228 return ret;
229 }
230
231 // If $RAVENWOOD_LOG_OUT is set, redirect stdout/err to this file.
232 // Originally it was added to allow to monitor log in realtime, with
233 // RAVENWOOD_LOG_OUT=$(tty) atest...
234 //
235 // As a special case, if $RAVENWOOD_LOG_OUT is set to "-", we try to find
236 // atest's process and send the output to its stdout. It's sort of hacky, but
237 // this allows shell redirection to work on Ravenwood output too,
238 // so e.g. `atest ... |tee atest.log` would work on Ravenwood's output.
239 // (which wouldn't work with `RAVENWOOD_LOG_OUT=$(tty)`).
240 //
241 // Otherwise -- if $RAVENWOOD_LOG_OUT isn't set -- atest/tradefed just writes
242 // the test's output to its own log file.
maybeRedirectLog()243 static void maybeRedirectLog() {
244 auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
245 if (ravenwoodLogOut == NULL || *ravenwoodLogOut == '\0') {
246 return;
247 }
248 std::string path;
249 if (strcmp("-", ravenwoodLogOut) == 0) {
250 pid_t ppid = find_atest_pid();
251 if (ppid < 0) {
252 ALOGI("RAVENWOOD_LOG_OUT set to '-', but unable to find atest's PID");
253 return;
254 }
255 path = std::format("/proc/{}/fd/1", ppid);
256 } else {
257 path = ravenwoodLogOut;
258 }
259 ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to '%s'", path.c_str());
260
261 // Redirect stdin / stdout to /dev/tty.
262 int ttyFd = open(path.c_str(), O_WRONLY | O_APPEND);
263 if (ttyFd == -1) {
264 ALOGW("$RAVENWOOD_LOG_OUT is set, but failed to open '%s': %s ", path.c_str(),
265 strerror(errno));
266 return;
267 }
268 dup2(ttyFd, 1);
269 dup2(ttyFd, 2);
270 }
271
272 static const JNINativeMethod sMethods[] = {
reloadNativeLibrary(Ljava/lang/String;)273 {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
getSystemProperty(Ljava/lang/String;)274 {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
setSystemProperty(Ljava/lang/String;Ljava/lang/String;)275 {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
removeSystemProperty(Ljava/lang/String;)276 {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
277 };
278
JNI_OnLoad(JavaVM * vm,void *)279 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
280 ALOGV("%s: JNI_OnLoad", __FILE__);
281
282 maybeRedirectLog();
283
284 JNIEnv* env = GetJNIEnvOrDie(vm);
285 gVM = vm;
286
287 // Fetch several references for future use
288 gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
289 gCheckSystemPropertyAccess =
290 GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
291 "(Ljava/lang/String;Z)V");
292
293 // Expose raw property methods as JNI methods
294 jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
295 if (res < 0) return -1;
296
297 return JNI_VERSION_1_4;
298 }
299