1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.incubator.jpackage.internal; 26 27 import java.io.IOException; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.util.*; 31 import java.util.function.Predicate; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 /** 38 * Builds list of packages providing dynamic libraries for the given set of files. 39 */ 40 final public class LibProvidersLookup { 41 static boolean supported() { 42 return (new ToolValidator(TOOL_LDD).validate() == null); 43 } 44 45 public LibProvidersLookup() { 46 } 47 48 LibProvidersLookup setPackageLookup(PackageLookup v) { 49 packageLookup = v; 50 return this; 51 } 52 53 List<String> execute(Path root) throws IOException { 54 // Get the list of files in the root for which to look up for needed shared libraries 55 List<Path> allPackageFiles; 56 try (Stream<Path> stream = Files.walk(root)) { 57 allPackageFiles = stream.filter(Files::isRegularFile).filter( 58 LibProvidersLookup::canDependOnLibs).collect( 59 Collectors.toList()); 60 } 61 62 Collection<Path> neededLibs = getNeededLibsForFiles(allPackageFiles); 63 64 // Get the list of unique package names. 65 List<String> neededPackages = neededLibs.stream().map(libPath -> { 66 try { 67 List<String> packageNames = packageLookup.apply(libPath).filter( 68 Objects::nonNull).filter(Predicate.not(String::isBlank)).distinct().collect( 69 Collectors.toList()); 70 Log.verbose(String.format("%s is provided by %s", libPath, packageNames)); 71 return packageNames; 72 } catch (IOException ex) { 73 // Ignore and keep going 74 Log.verbose(ex); 75 List<String> packageNames = Collections.emptyList(); 76 return packageNames; 77 } 78 }).flatMap(List::stream).sorted().distinct().collect(Collectors.toList()); 79 80 return neededPackages; 81 } 82 83 private static List<Path> getNeededLibsForFile(Path path) throws IOException { 84 List<Path> result = new ArrayList<>(); 85 int ret = Executor.of(TOOL_LDD, path.toString()).setOutputConsumer(lines -> { 86 lines.map(line -> { 87 Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line); 88 if (matcher.find()) { 89 return matcher.group(1); 90 } 91 return null; 92 }).filter(Objects::nonNull).map(Path::of).forEach(result::add); 93 }).execute(); 94 95 if (ret != 0) { 96 // objdump failed. This is OK if the tool was applied to not a binary file 97 return Collections.emptyList(); 98 } 99 100 return result; 101 } 102 103 private static Collection<Path> getNeededLibsForFiles(List<Path> paths) { 104 // Depending on tool used, the set can contain full paths (ldd) or 105 // only file names (objdump). 106 Set<Path> allLibs = paths.stream().map(path -> { 107 List<Path> libs; 108 try { 109 libs = getNeededLibsForFile(path); 110 } catch (IOException ex) { 111 Log.verbose(ex); 112 libs = Collections.emptyList(); 113 } 114 return libs; 115 }).flatMap(List::stream).collect(Collectors.toSet()); 116 117 // `allLibs` contains names of all .so needed by files from `paths` list. 118 // If there are mutual dependencies between binaries from `paths` list, 119 // then names or full paths to these binaries are in `allLibs` set. 120 // Remove these items from `allLibs`. 121 Set<Path> excludedNames = paths.stream().map(Path::getFileName).collect( 122 Collectors.toSet()); 123 Iterator<Path> it = allLibs.iterator(); 124 while (it.hasNext()) { 125 Path libName = it.next().getFileName(); 126 if (excludedNames.contains(libName)) { 127 it.remove(); 128 } 129 } 130 131 return allLibs; 132 } 133 134 private static boolean canDependOnLibs(Path path) { 135 return path.toFile().canExecute() || path.toString().endsWith(".so"); 136 } 137 138 @FunctionalInterface 139 public interface PackageLookup { 140 Stream<String> apply(Path path) throws IOException; 141 } 142 143 private PackageLookup packageLookup; 144 145 private static final String TOOL_LDD = "ldd"; 146 147 // 148 // Typical ldd output: 149 // 150 // ldd: warning: you do not have execution permission for `/tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt_headless.so' 151 // linux-vdso.so.1 => (0x00007ffce6bfd000) 152 // libawt.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt.so (0x00007f4e00c75000) 153 // libjvm.so => not found 154 // libjava.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libjava.so (0x00007f4e00c41000) 155 // libm.so.6 => /lib64/libm.so.6 (0x00007f4e00834000) 156 // libdl.so.2 => /lib64/libdl.so.2 (0x00007f4e00630000) 157 // libc.so.6 => /lib64/libc.so.6 (0x00007f4e00262000) 158 // libjvm.so => not found 159 // libjvm.so => not found 160 // libverify.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libverify.so (0x00007f4e00c2e000) 161 // /lib64/ld-linux-x86-64.so.2 (0x00007f4e00b36000) 162 // libjvm.so => not found 163 // 164 private static final Pattern LIB_IN_LDD_OUTPUT_REGEX = Pattern.compile( 165 "^\\s*\\S+\\s*=>\\s*(\\S+)\\s+\\(0[xX]\\p{XDigit}+\\)"); 166 }