Z3
 
Loading...
Searching...
No Matches
NativeLibraryLoader.java
Go to the documentation of this file.
1
5package com.microsoft.z3;
6
7import java.io.FileNotFoundException;
8import java.io.IOException;
9import java.io.InputStream;
10import java.nio.file.Files;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13import java.nio.file.LinkOption;
14import java.nio.file.DirectoryStream;
15import java.nio.file.StandardCopyOption;
16import java.nio.file.StandardOpenOption;
17import java.nio.file.attribute.PosixFilePermissions;
18import java.nio.file.attribute.PosixFilePermission;
19import java.nio.channels.FileChannel;
20import java.nio.channels.FileLock;
21import java.nio.channels.OverlappingFileLockException;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.Locale;
25import java.util.Set;
26import java.util.TreeMap;
27
29final class NativeLibraryLoader {
30 private NativeLibraryLoader() { }
31
32 // Called only from Native's class initializer, which the JVM serializes.
33 static void load() {
34 if (Boolean.parseBoolean(System.getProperty("z3.skipLibraryLoad"))) {
35 return;
36 }
37 UnsatisfiedLinkError first;
38 try {
39 System.loadLibrary("z3java");
40 return;
41 } catch (UnsatisfiedLinkError ex) {
42 first = ex;
43 }
44 UnsatisfiedLinkError second;
45 try {
46 System.loadLibrary("libz3java");
47 return;
48 } catch (UnsatisfiedLinkError ex) {
49 second = ex;
50 }
51
52 String platform = platform();
53 String stage = "finding bundled resources for " + platform;
54 Throwable cacheFailure = null;
55 try {
56 String core = libraryName(platform, "z3");
57 String bridge = libraryName(platform, "z3java");
58 try {
59 if (!"false".equalsIgnoreCase(System.getProperty("z3.nativeCache"))) {
60 loadCached(platform, core, bridge);
61 return;
62 }
63 } catch (IOException | UnsatisfiedLinkError | SecurityException
64 | OverlappingFileLockException | java.nio.file.InvalidPathException
65 | UnsupportedOperationException ex) {
66 // Restricted properties/filesystems and other ClassLoaders keep the private path.
67 cacheFailure = ex;
68 }
69 // Check both before creating a directory or loading either library.
70 try (InputStream coreInput = resource(platform, core);
71 InputStream bridgeInput = resource(platform, bridge)) {
72 Path directory = Files.createTempDirectory("z3-native-");
73 directory.toFile().deleteOnExit();
74 stage = "extracting bundled resources for " + platform + " to " + directory;
75 Path corePath = extract(directory, core, coreInput);
76 Path bridgePath = extract(directory, bridge, bridgeInput);
77 stage = "loading " + corePath;
78 System.load(corePath.toAbsolutePath().toString());
79 stage = "loading " + bridgePath;
80 System.load(bridgePath.toAbsolutePath().toString());
81 }
82 } catch (IOException | UnsatisfiedLinkError | SecurityException ex) {
83 UnsatisfiedLinkError error = new UnsatisfiedLinkError(
84 "Failed to load Z3 native libraries for " + platform + " while " + stage
85 + ". System attempts: z3java: " + first.getMessage()
86 + "; libz3java: " + second.getMessage()
87 + (cacheFailure == null ? "" : ". Cache attempt: " + cacheFailure)
88 + ". Bundled attempt: " + ex.getMessage()
89 + ". Check the platform classifier, native runtime dependencies and writable java.io.tmpdir.");
90 error.initCause(ex);
91 error.addSuppressed(first);
92 error.addSuppressed(second);
93 if (cacheFailure != null) {
94 ex.addSuppressed(cacheFailure);
95 }
96 throw error;
97 }
98 }
99
100 private static InputStream resource(String platform, String name) throws IOException {
101 String path = "/com/microsoft/z3/native/" + platform + "/" + name;
102 InputStream input = NativeLibraryLoader.class.getResourceAsStream(path);
103 if (input == null) {
104 throw new FileNotFoundException("Missing bundled native resource " + path);
105 }
106 return input;
107 }
108
109 private static Path extract(Path directory, String name, InputStream input) throws IOException {
110 Path target = directory.resolve(name);
111 // Private fallback for disabled/unavailable caching or another ClassLoader.
112 target.toFile().deleteOnExit();
113 Files.copy(input, target);
114 return target;
115 }
116
117 private static MessageDigest digest() {
118 try {
119 return MessageDigest.getInstance("SHA-256");
120 } catch (NoSuchAlgorithmException ex) {
121 throw new AssertionError("SHA-256 is required by Java", ex);
122 }
123 }
124
125 private static String hash(InputStream input) throws IOException {
126 MessageDigest digest = digest();
127 byte[] buffer = new byte[65536];
128 int count;
129 while ((count = input.read(buffer)) != -1) {
130 digest.update(buffer, 0, count);
131 }
132 StringBuilder text = new StringBuilder(64);
133 for (byte value : digest.digest()) {
134 text.append(Character.forDigit((value & 255) >>> 4, 16));
135 text.append(Character.forDigit(value & 15, 16));
136 }
137 return text.toString();
138 }
139
140 private static boolean matches(Path path, String expected) throws IOException {
141 if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
142 return false;
143 }
144 try (InputStream input = Files.newInputStream(path, LinkOption.NOFOLLOW_LINKS)) {
145 return expected.equals(hash(input));
146 }
147 }
148
149 private static void checkPath(Path path) throws IOException {
150 for (Path part = path; part != null; part = part.getParent()) {
151 if (Files.isSymbolicLink(part)) {
152 throw new IOException("Symbolic link in native cache path: " + part);
153 }
154 }
155 }
156
157 private static Path cacheRoot() throws IOException {
158 String configured = System.getProperty("z3.nativeCacheDir");
159 String profile = System.getProperty("user.home");
160 if (profile == null || profile.isEmpty()) {
161 throw new IOException("No user.home for the native cache");
162 }
163 Path root = (configured == null
164 ? Paths.get(profile, ".z3", "native")
165 : Paths.get(configured)).toAbsolutePath().normalize();
166 checkPath(root);
167 if (root.getFileSystem().supportedFileAttributeViews().contains("posix")) {
168 Files.createDirectories(root, PosixFilePermissions.asFileAttribute(
169 PosixFilePermissions.fromString("rwx------")));
170 Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(root);
171 if (permissions.contains(PosixFilePermission.GROUP_WRITE)
172 || permissions.contains(PosixFilePermission.OTHERS_WRITE)) {
173 throw new IOException("Native cache is writable by other users: " + root);
174 }
175 } else {
176 // On Windows inherit the user's profile ACL, including for .z3/native.
177 Files.createDirectories(root);
178 }
179 checkPath(root);
180 if (!Files.getOwner(root).equals(Files.getOwner(Paths.get(System.getProperty("user.home"))))) {
181 throw new IOException("Native cache is not owned by the profile owner: " + root);
182 }
183 return root;
184 }
185
186 private static void loadCached(String platform, String core, String bridge) throws IOException {
187 // Interned strings are shared across ClassLoaders. Keep one cache channel open
188 // per JVM: on some systems closing another channel releases this JVM's locks.
189 synchronized ("com.microsoft.z3.NativeLibraryLoader.cache") {
190 loadCachedLocked(platform, core, bridge);
191 }
192 }
193
194 private static void loadCachedLocked(String platform, String core, String bridge) throws IOException {
195 String coreHash;
196 String bridgeHash;
197 // Derive identity from the actual bundled bytes, not the version or a mutable manifest.
198 try (InputStream input = resource(platform, core)) {
199 coreHash = hash(input);
200 }
201 try (InputStream input = resource(platform, bridge)) {
202 bridgeHash = hash(input);
203 }
204 String identity;
205 try (InputStream input = new java.io.ByteArrayInputStream(
206 (coreHash + bridgeHash).getBytes(java.nio.charset.StandardCharsets.US_ASCII))) {
207 identity = hash(input);
208 }
209 Path root = cacheRoot();
210 // One process lock covers verification, publication, loading and eviction.
211 // File locks overlap across ClassLoaders in one JVM; that case uses private copies.
212 try (FileChannel channel = FileChannel.open(root.resolve(".lock"),
213 StandardOpenOption.CREATE, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS);
214 FileLock lock = channel.lock()) {
215 Path directory = root.resolve(platform + "-" + identity);
216 checkPath(directory);
217 if (!matches(directory.resolve(core), coreHash) || !matches(directory.resolve(bridge), bridgeHash)) {
218 if (Files.exists(directory, LinkOption.NOFOLLOW_LINKS)) {
219 removeEntry(directory, core, bridge);
220 }
221 Path staging = Files.createTempDirectory(root, ".extract-");
222 try {
223 try (InputStream input = resource(platform, core)) {
224 Files.copy(input, staging.resolve(core));
225 }
226 try (InputStream input = resource(platform, bridge)) {
227 Files.copy(input, staging.resolve(bridge));
228 }
229 if (!matches(staging.resolve(core), coreHash) || !matches(staging.resolve(bridge), bridgeHash)) {
230 throw new IOException("Bundled native resources changed during extraction");
231 }
232 // Publish only complete, verified pairs. If atomic moves are unavailable,
233 // preserve the old private-extraction behavior instead of a partial cache.
234 Files.move(staging, directory, StandardCopyOption.ATOMIC_MOVE);
235 } finally {
236 if (Files.exists(staging, LinkOption.NOFOLLOW_LINKS)) {
237 removeEntry(staging, core, bridge);
238 }
239 }
240 }
241 Files.setLastModifiedTime(directory, java.nio.file.attribute.FileTime.fromMillis(System.currentTimeMillis()));
242 System.load(directory.resolve(core).toString());
243 System.load(directory.resolve(bridge).toString());
244 prune(root, directory, platform, core, bridge);
245 }
246 }
247
248 // Never recursively remove a directory, follow links, or touch unknown files.
249 private static void checkEntry(Path directory, String core, String bridge) throws IOException {
250 if (!Files.isDirectory(directory, LinkOption.NOFOLLOW_LINKS)) {
251 throw new IOException("Not a native cache directory: " + directory);
252 }
253 try (DirectoryStream<Path> files = Files.newDirectoryStream(directory)) {
254 for (Path file : files) {
255 String name = file.getFileName().toString();
256 if ((!name.equals(core) && !name.equals(bridge))
257 || !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
258 throw new IOException("Unexpected native cache entry: " + file);
259 }
260 }
261 }
262 }
263
264 private static void removeEntry(Path directory, String core, String bridge) throws IOException {
265 checkEntry(directory, core, bridge);
266 Files.deleteIfExists(directory.resolve(core));
267 Files.deleteIfExists(directory.resolve(bridge));
268 Files.delete(directory);
269 }
270
271 private static void prune(Path root, Path current, String platform, String core, String bridge) {
272 try {
273 TreeMap<String, Path> previous = new TreeMap<String, Path>();
274 long stale = System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000;
275 try (DirectoryStream<Path> entries = Files.newDirectoryStream(root)) {
276 for (Path entry : entries) {
277 String name = entry.getFileName().toString();
278 if (entry.equals(current) || !Files.isDirectory(entry, LinkOption.NOFOLLOW_LINKS)) {
279 continue;
280 }
281 long modified = Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
282 if (name.matches(platform + "-[0-9a-f]{64}")) {
283 try { checkEntry(entry, core, bridge); } catch (IOException ignored) { continue; }
284 previous.put(String.format(Locale.ROOT, "%020d-%s", modified, name), entry);
285 } else if (name.startsWith(".extract-") && modified < stale) {
286 try { removeEntry(entry, core, bridge); } catch (IOException ignored) { }
287 }
288 }
289 }
290 // Keep this pair and the two most recently used pairs for this platform.
291 while (previous.size() > 2) {
292 Path entry = previous.pollFirstEntry().getValue();
293 try { removeEntry(entry, core, bridge); } catch (IOException ignored) { }
294 }
295 } catch (IOException | SecurityException ignored) {
296 // A loaded DLL may remain locked on Windows. Cleanup cannot break a working load.
297 }
298 }
299
300 private static String libraryName(String platform, String name) {
301 if (platform.startsWith("win-")) {
302 return name.equals("z3") ? "libz3.dll" : "z3java.dll";
303 }
304 return "lib" + name + (platform.startsWith("osx-") ? ".dylib" : ".so");
305 }
306
307 static String platform() {
308 String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
309 String arch = System.getProperty("os.arch", "").toLowerCase(Locale.ROOT);
310 String osPart;
311 if (os.startsWith("windows")) {
312 osPart = "win";
313 } else if (os.startsWith("mac") || os.equals("darwin")) {
314 osPart = "osx";
315 } else if (os.equals("linux")) {
316 osPart = "linux";
317 } else {
318 osPart = "unsupported-os";
319 }
320 String archPart;
321 if (arch.equals("amd64") || arch.equals("x86_64")) {
322 archPart = "x64";
323 } else if (arch.equals("aarch64") || arch.equals("arm64")) {
324 archPart = "arm64";
325 } else if (arch.equals("x86") || arch.matches("i[3-6]86")) {
326 archPart = "x86";
327 } else {
328 archPart = "unsupported-arch";
329 }
330 return osPart + "-" + archPart;
331 }
332}
String(name, ctx=None)
Definition z3py.py:11932