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 package com.android.ravenwood.common;
17 
18 import com.android.ravenwood.common.divergence.RavenwoodDivergence;
19 
20 import java.io.File;
21 import java.io.FileDescriptor;
22 import java.io.FileInputStream;
23 import java.io.PrintStream;
24 import java.util.Arrays;
25 
26 public class RavenwoodCommonUtils {
27     private static final String TAG = "RavenwoodCommonUtils";
28 
RavenwoodCommonUtils()29     private RavenwoodCommonUtils() {
30     }
31 
32     private static final Object sLock = new Object();
33 
34     /** Name of `libravenwood_runtime` */
35     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
36 
37     /** Directory name of `out/host/linux-x86/testcases/ravenwood-runtime` */
38     private static final String RAVENWOOD_RUNTIME_DIR_NAME = "ravenwood-runtime";
39 
40     private static boolean sEnableExtraRuntimeCheck =
41             "1".equals(System.getenv("RAVENWOOD_ENABLE_EXTRA_RUNTIME_CHECK"));
42 
43     private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
44 
45     private static final String RAVEWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
46 
47     public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
48 
49     // @GuardedBy("sLock")
50     private static boolean sIntegrityChecked = false;
51 
52     /**
53      * @return if we're running on Ravenwood.
54      */
isOnRavenwood()55     public static boolean isOnRavenwood() {
56         return IS_ON_RAVENWOOD;
57     }
58 
59     /**
60      * Throws if the runtime is not Ravenwood.
61      */
ensureOnRavenwood()62     public static void ensureOnRavenwood() {
63         if (!isOnRavenwood()) {
64             throw new RavenwoodRuntimeException("This is only supposed to be used on Ravenwood");
65         }
66     }
67 
68     /**
69      * @return if the various extra runtime check should be enabled.
70      */
shouldEnableExtraRuntimeCheck()71     public static boolean shouldEnableExtraRuntimeCheck() {
72         return sEnableExtraRuntimeCheck;
73     }
74 
75     /**
76      * Load the main runtime JNI library.
77      */
loadRavenwoodNativeRuntime()78     public static void loadRavenwoodNativeRuntime() {
79         ensureOnRavenwood();
80         loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
81     }
82 
83     /**
84      * Internal implementation of
85      * {@link android.platform.test.ravenwood.RavenwoodUtils#loadJniLibrary(String)}
86      */
loadJniLibrary(String libname)87     public static void loadJniLibrary(String libname) {
88         if (RavenwoodCommonUtils.isOnRavenwood()) {
89             loadJniLibraryInternal(libname);
90         } else {
91             System.loadLibrary(libname);
92         }
93     }
94 
95     /**
96      * Function equivalent to ART's System.loadLibrary. See RavenwoodUtils for why we need it.
97      */
loadJniLibraryInternal(String libname)98     private static void loadJniLibraryInternal(String libname) {
99         var path = System.getProperty("java.library.path");
100         var filename = "lib" + libname + ".so";
101 
102         System.out.println("Looking for library " + libname + ".so in java.library.path:" + path);
103 
104         try {
105             if (path == null) {
106                 throw new UnsatisfiedLinkError("Cannot load library " + libname + "."
107                         + " Property java.library.path not set!");
108             }
109             for (var dir : path.split(":")) {
110                 var file = new File(dir + "/" + filename);
111                 if (file.exists()) {
112                     System.load(file.getAbsolutePath());
113                     return;
114                 }
115             }
116             throw new UnsatisfiedLinkError("Library " + libname + " not found in "
117                     + "java.library.path: " + path);
118         } catch (Throwable e) {
119             dumpFiles(System.out);
120             throw e;
121         }
122     }
123 
dumpFiles(PrintStream out)124     private static void dumpFiles(PrintStream out) {
125         try {
126             var path = System.getProperty("java.library.path");
127             out.println("# java.library.path=" + path);
128 
129             for (var dir : path.split(":")) {
130                 listFiles(out, new File(dir), "");
131 
132                 var gparent = new File((new File(dir)).getAbsolutePath() + "../../..")
133                         .getCanonicalFile();
134                 if (gparent.getName().contains("testcases")) {
135                     // Special case: if we found this directory, dump its contents too.
136                     listFiles(out, gparent, "");
137                 }
138             }
139 
140             var gparent = new File("../..").getCanonicalFile();
141             out.println("# ../..=" + gparent);
142             listFiles(out, gparent, "");
143         } catch (Throwable th) {
144             out.println("Error: " + th.toString());
145             th.printStackTrace(out);
146         }
147     }
148 
listFiles(PrintStream out, File dir, String prefix)149     private static void listFiles(PrintStream out, File dir, String prefix) {
150         if (!dir.isDirectory()) {
151             out.println(prefix + dir.getAbsolutePath() + " is not a directory!");
152             return;
153         }
154         out.println(prefix + ":" + dir.getAbsolutePath() + "/");
155         // First, list the files.
156         for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
157             out.println(prefix + "  " + file.getName() + "" + (file.isDirectory() ? "/" : ""));
158         }
159 
160         // Then recurse.
161         if (dir.getAbsolutePath().startsWith("/usr") || dir.getAbsolutePath().startsWith("/lib")) {
162             // There would be too many files, so don't recurse.
163             return;
164         }
165         for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
166             if (file.isDirectory()) {
167                 listFiles(out, file, prefix + "  ");
168             }
169         }
170     }
171 
172     /**
173      * @return the full directory path that contains the "ravenwood-runtime" files.
174      *
175      * This method throws if called on the device side.
176      */
getRavenwoodRuntimePath()177     public static String getRavenwoodRuntimePath() {
178         ensureOnRavenwood();
179         return RAVEWOOD_RUNTIME_PATH;
180     }
181 
getRavenwoodRuntimePathInternal()182     private static String getRavenwoodRuntimePathInternal() {
183         if (!isOnRavenwood()) {
184             return null;
185         }
186         var path = System.getProperty("java.library.path");
187 
188         System.out.println("Looking for " + RAVENWOOD_RUNTIME_DIR_NAME + " directory"
189                 + " in java.library.path:" + path);
190 
191         try {
192             if (path == null) {
193                 throw new IllegalStateException("java.library.path shouldn't be null");
194             }
195             for (var dir : path.split(":")) {
196 
197                 // For each path, see if the path contains RAVENWOOD_RUNTIME_DIR_NAME.
198                 var d = new File(dir);
199                 for (;;) {
200                     if (d.getParent() == null) {
201                         break; // Root dir, stop.
202                     }
203                     if (RAVENWOOD_RUNTIME_DIR_NAME.equals(d.getName())) {
204                         var ret = d.getAbsolutePath() + "/";
205                         System.out.println("Found: " + ret);
206                         return ret;
207                     }
208                     d = d.getParentFile();
209                 }
210             }
211             throw new IllegalStateException(RAVENWOOD_RUNTIME_DIR_NAME + " not found");
212         } catch (Throwable e) {
213             dumpFiles(System.out);
214             throw e;
215         }
216     }
217 
218     /** Close an {@link AutoCloseable}. */
closeQuietly(AutoCloseable c)219     public static void closeQuietly(AutoCloseable c) {
220         if (c != null) {
221             try {
222                 c.close();
223             } catch (Exception e) {
224                 // Ignore
225             }
226         }
227     }
228 
229     /** Close a {@link FileDescriptor}. */
closeQuietly(FileDescriptor fd)230     public static void closeQuietly(FileDescriptor fd) {
231         var is = new FileInputStream(fd);
232         RavenwoodCommonUtils.closeQuietly(is);
233     }
234 }
235