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 }