1 /*
   2  * Copyright (c) 2007, 2016, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.nio.file.Path;
  25 import java.io.BufferedReader;
  26 import java.io.ByteArrayOutputStream;
  27 import java.io.Closeable;
  28 import java.io.File;
  29 import java.io.FileFilter;
  30 import java.io.FileOutputStream;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.InputStreamReader;
  34 import java.io.PrintStream;
  35 import java.net.URI;
  36 import java.net.URL;
  37 import java.nio.charset.Charset;
  38 import java.nio.file.attribute.BasicFileAttributes;
  39 import java.nio.file.FileSystem;
  40 import java.nio.file.FileSystems;
  41 import java.nio.file.FileVisitResult;
  42 import java.nio.file.FileVisitor;
  43 import java.nio.file.Files;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.Collections;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.HashMap;
  50 import java.util.jar.JarFile;
  51 import java.util.jar.JarOutputStream;
  52 import java.util.jar.Pack200;
  53 import java.util.zip.ZipEntry;
  54 import java.util.zip.ZipFile;
  55 
  56 import static java.nio.file.StandardCopyOption.*;
  57 import static java.nio.file.StandardOpenOption.*;
  58 
  59 /**
  60  *
  61  * @author ksrini
  62  */
  63 
  64 /*
  65  * This class contains the commonly used utilities.
  66  */
  67 class Utils {
  68     static final String JavaHome = System.getProperty("test.java",
  69             System.getProperty("java.home"));
  70     static final boolean IsWindows =
  71             System.getProperty("os.name").startsWith("Windows");
  72     static final boolean Is64Bit =
  73             System.getProperty("sun.arch.data.model", "32").equals("64");
  74     static final File   JavaSDK =  new File(JavaHome);
  75 
  76     static final String PACK_FILE_EXT   = ".pack";
  77     static final String JAVA_FILE_EXT   = ".java";
  78     static final String CLASS_FILE_EXT  = ".class";
  79     static final String JAR_FILE_EXT    = ".jar";
  80 
  81     static final File   TEST_SRC_DIR = new File(System.getProperty("test.src"));
  82     static final File   TEST_CLS_DIR = new File(System.getProperty("test.classes"));
  83     static final String VERIFIER_DIR_NAME = "pack200-verifier";
  84     static final File   VerifierJar = new File(VERIFIER_DIR_NAME + JAR_FILE_EXT);
  85     static final File   XCLASSES = new File("xclasses");
  86 
  87     private Utils() {} // all static
  88 
  89     private static void init() throws IOException {
  90         if (VerifierJar.exists()) {
  91             return;
  92         }
  93         File srcDir = new File(getVerifierDir(), "src");
  94         List<File> javaFileList = findFiles(srcDir, createFilter(JAVA_FILE_EXT));
  95         File tmpFile = File.createTempFile("javac", ".tmp");
  96         XCLASSES.mkdirs();
  97         FileOutputStream fos = null;
  98         PrintStream ps = null;
  99         try {
 100             fos = new FileOutputStream(tmpFile);
 101             ps = new PrintStream(fos);
 102             for (File f : javaFileList) {
 103                 ps.println(f.getAbsolutePath());
 104             }
 105         } finally {
 106             close(ps);
 107             close(fos);
 108         }
 109 
 110         compiler("-d",
 111                 XCLASSES.getName(),
 112                 "-XaddExports:jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED",
 113                 "@" + tmpFile.getAbsolutePath());
 114 
 115         jar("cvfe",
 116             VerifierJar.getName(),
 117             "sun.tools.pack.verify.Main",
 118             "-C",
 119             XCLASSES.getName(),
 120             ".");
 121     }
 122 
 123     private static File getVerifierDir() {
 124         File srcDir = new File(TEST_SRC_DIR, VERIFIER_DIR_NAME);
 125         if (!srcDir.exists()) {
 126             // if not available try one level above
 127             srcDir = new File(TEST_SRC_DIR.getParentFile(), VERIFIER_DIR_NAME);
 128         }
 129         return srcDir;
 130     }
 131 
 132     static File getGoldenJar() {
 133         return new File(new File(getVerifierDir(), "data"), "golden.jar");
 134     }
 135     static void dirlist(File dir) {
 136         File[] files = dir.listFiles();
 137         System.out.println("--listing " + dir.getAbsolutePath() + "---");
 138         for (File f : files) {
 139             StringBuffer sb = new StringBuffer();
 140             sb.append(f.isDirectory() ? "d " : "- ");
 141             sb.append(f.getName());
 142             System.out.println(sb);
 143         }
 144     }
 145     static void doCompareVerify(File reference, File specimen) throws IOException {
 146         init();
 147         List<String> cmds = new ArrayList<String>();
 148         cmds.add(getJavaCmd());
 149         cmds.add("-XaddExports:jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED");
 150         cmds.add("-cp");
 151         cmds.add(VerifierJar.getName());
 152         cmds.add("sun.tools.pack.verify.Main");
 153         cmds.add(reference.getAbsolutePath());
 154         cmds.add(specimen.getAbsolutePath());
 155         cmds.add("-O");
 156         runExec(cmds);
 157     }
 158 
 159     static void doCompareBitWise(File reference, File specimen)
 160             throws IOException {
 161         init();
 162         List<String> cmds = new ArrayList<String>();
 163         cmds.add(getJavaCmd());
 164         cmds.add("-cp");
 165         cmds.add(VerifierJar.getName());
 166         cmds.add("sun.tools.pack.verify.Main");
 167         cmds.add(reference.getName());
 168         cmds.add(specimen.getName());
 169         cmds.add("-O");
 170         cmds.add("-b");
 171         runExec(cmds);
 172     }
 173 
 174     static FileFilter createFilter(final String extension) {
 175         return new FileFilter() {
 176             @Override
 177             public boolean accept(File pathname) {
 178                 String name = pathname.getName();
 179                 if (name.endsWith(extension)) {
 180                     return true;
 181                 }
 182                 return false;
 183             }
 184         };
 185     }
 186 
 187     /*
 188      * clean up all the usual suspects
 189      */
 190     static void cleanup() throws IOException {
 191         recursiveDelete(XCLASSES);
 192         List<File> toDelete = new ArrayList<>();
 193         toDelete.addAll(Utils.findFiles(new File("."),
 194                 Utils.createFilter(".out")));
 195         toDelete.addAll(Utils.findFiles(new File("."),
 196                 Utils.createFilter(".bak")));
 197         toDelete.addAll(Utils.findFiles(new File("."),
 198                 Utils.createFilter(".jar")));
 199         toDelete.addAll(Utils.findFiles(new File("."),
 200                 Utils.createFilter(".pack")));
 201         toDelete.addAll(Utils.findFiles(new File("."),
 202                 Utils.createFilter(".bnd")));
 203         toDelete.addAll(Utils.findFiles(new File("."),
 204                 Utils.createFilter(".txt")));
 205         toDelete.addAll(Utils.findFiles(new File("."),
 206                 Utils.createFilter(".idx")));
 207         toDelete.addAll(Utils.findFiles(new File("."),
 208                 Utils.createFilter(".gidx")));
 209         for (File f : toDelete) {
 210             f.delete();
 211         }
 212     }
 213 
 214     static final FileFilter DIR_FILTER = new FileFilter() {
 215         public boolean accept(File pathname) {
 216             if (pathname.isDirectory()) {
 217                 return true;
 218             }
 219             return false;
 220         }
 221     };
 222 
 223     static final FileFilter FILE_FILTER = new FileFilter() {
 224         public boolean accept(File pathname) {
 225             if (pathname.isFile()) {
 226                 return true;
 227             }
 228             return false;
 229         }
 230     };
 231 
 232     static void copyFile(File src, File dst) throws IOException {
 233         Path parent = dst.toPath().getParent();
 234         if (parent != null) {
 235             Files.createDirectories(parent);
 236         }
 237         Files.copy(src.toPath(), dst.toPath(), COPY_ATTRIBUTES, REPLACE_EXISTING);
 238         if (dst.isDirectory() && !dst.canWrite()) {
 239             dst.setWritable(true);
 240         }
 241     }
 242 
 243     static String baseName(File file, String extension) {
 244         return baseName(file.getAbsolutePath(), extension);
 245     }
 246 
 247     static String baseName(String name, String extension) {
 248         int cut = name.length() - extension.length();
 249         return name.lastIndexOf(extension) == cut
 250                 ? name.substring(0, cut)
 251                 : name;
 252 
 253     }
 254    static void createFile(File outFile, List<String> content) throws IOException {
 255         Files.write(outFile.getAbsoluteFile().toPath(), content,
 256                 Charset.defaultCharset(), CREATE_NEW, TRUNCATE_EXISTING);
 257     }
 258 
 259     /*
 260      * Suppose a path is provided which consists of a full path
 261      * this method returns the sub path for a full path ex: /foo/bar/baz/foobar.z
 262      * and the base path is /foo/bar it will will return baz/foobar.z.
 263      */
 264     private static String getEntryPath(String basePath, String fullPath) {
 265         if (!fullPath.startsWith(basePath)) {
 266             return null;
 267         }
 268         return fullPath.substring(basePath.length());
 269     }
 270 
 271     static String getEntryPath(File basePathFile, File fullPathFile) {
 272         return getEntryPath(basePathFile.toString(), fullPathFile.toString());
 273     }
 274 
 275     public static void recursiveCopy(File src, File dest) throws IOException {
 276         if (!src.exists() || !src.canRead()) {
 277             throw new IOException("file not found or readable: " + src);
 278         }
 279         if (dest.exists() && !dest.isDirectory() && !dest.canWrite()) {
 280             throw new IOException("file not found or writeable: " + dest);
 281         }
 282         if (!dest.exists()) {
 283             dest.mkdirs();
 284         }
 285         List<File> a = directoryList(src);
 286         for (File f : a) {
 287             copyFile(f, new File(dest, getEntryPath(src, f)));
 288         }
 289     }
 290 
 291     static List<File> directoryList(File dirname) {
 292         List<File>  dirList = new ArrayList<File>();
 293         return directoryList(dirname, dirList, null);
 294     }
 295 
 296     private static List<File> directoryList(File dirname, List<File> dirList,
 297             File[] dirs) {
 298         dirList.addAll(Arrays.asList(dirname.listFiles(FILE_FILTER)));
 299         dirs = dirname.listFiles(DIR_FILTER);
 300         for (File f : dirs) {
 301             if (f.isDirectory() && !f.equals(dirname)) {
 302                 dirList.add(f);
 303                 directoryList(f, dirList, dirs);
 304             }
 305         }
 306         return dirList;
 307     }
 308 
 309     static void recursiveDelete(File dir) throws IOException {
 310         if (dir.isFile()) {
 311             dir.delete();
 312         } else if (dir.isDirectory()) {
 313             File[] entries = dir.listFiles();
 314             for (int i = 0; i < entries.length; i++) {
 315                 if (entries[i].isDirectory()) {
 316                     recursiveDelete(entries[i]);
 317                 }
 318                 entries[i].delete();
 319             }
 320             dir.delete();
 321         }
 322     }
 323 
 324     static List<File> findFiles(File startDir, FileFilter filter)
 325             throws IOException {
 326         List<File> list = new ArrayList<File>();
 327         findFiles0(startDir, list, filter);
 328         return list;
 329     }
 330     /*
 331      * finds files in the start directory using the the filter, appends
 332      * the files to the dirList.
 333      */
 334     private static void findFiles0(File startDir, List<File> list,
 335                                     FileFilter filter) throws IOException {
 336         File[] foundFiles = startDir.listFiles(filter);
 337         if (foundFiles == null) {
 338             return;
 339         }
 340         list.addAll(Arrays.asList(foundFiles));
 341         File[] dirs = startDir.listFiles(DIR_FILTER);
 342         for (File dir : dirs) {
 343             findFiles0(dir, list, filter);
 344         }
 345     }
 346 
 347     static void close(Closeable c) {
 348         if (c == null) {
 349             return;
 350         }
 351         try {
 352             c.close();
 353         } catch (IOException ignore) {
 354         }
 355     }
 356 
 357     static void compiler(String... javacCmds) {
 358         List<String> cmdList = new ArrayList<>();
 359         cmdList.add(getJavacCmd());
 360         for (String x : javacCmds) {
 361             cmdList.add(x);
 362         }
 363         runExec(cmdList);
 364     }
 365 
 366     static void jar(String... jargs) {
 367         List<String> cmdList = new ArrayList<>();
 368         cmdList.add(getJarCmd());
 369         for (String x : jargs) {
 370             cmdList.add(x);
 371         }
 372         runExec(cmdList);
 373     }
 374 
 375     static void testWithRepack(File inFile, String... repackOpts) throws IOException {
 376         File cwd = new File(".");
 377         // pack using --repack in native mode
 378         File nativejarFile = new File(cwd, "out-n" + Utils.JAR_FILE_EXT);
 379         repack(inFile, nativejarFile, false, repackOpts);
 380         doCompareVerify(inFile, nativejarFile);
 381 
 382         // ensure bit compatibility between the unpacker variants
 383         File javajarFile = new File(cwd, "out-j" + Utils.JAR_FILE_EXT);
 384         repack(inFile, javajarFile, true, repackOpts);
 385         doCompareBitWise(javajarFile, nativejarFile);
 386     }
 387 
 388     static List<String> repack(File inFile, File outFile,
 389             boolean disableNative, String... extraOpts) {
 390         List<String> cmdList = new ArrayList<>();
 391         cmdList.clear();
 392         cmdList.add(Utils.getJavaCmd());
 393         cmdList.add("-ea");
 394         cmdList.add("-esa");
 395         if (disableNative) {
 396             cmdList.add("-Dcom.sun.java.util.jar.pack.disable.native=true");
 397         }
 398         cmdList.add("com.sun.java.util.jar.pack.Driver");
 399         cmdList.add("--repack");
 400         if (extraOpts != null) {
 401            for (String opt: extraOpts) {
 402                cmdList.add(opt);
 403            }
 404         }
 405         cmdList.add(outFile.getName());
 406         cmdList.add(inFile.getName());
 407         return Utils.runExec(cmdList);
 408     }
 409 
 410     // given a jar file foo.jar will write to foo.pack
 411     static void pack(JarFile jarFile, File packFile) throws IOException {
 412         Pack200.Packer packer = Pack200.newPacker();
 413         Map<String, String> p = packer.properties();
 414         // Take the time optimization vs. space
 415         p.put(packer.EFFORT, "1");  // CAUTION: do not use 0.
 416         // Make the memory consumption as effective as possible
 417         p.put(packer.SEGMENT_LIMIT, "10000");
 418         // ignore all JAR deflation requests to save time
 419         p.put(packer.DEFLATE_HINT, packer.FALSE);
 420         // save the file ordering of the original JAR
 421         p.put(packer.KEEP_FILE_ORDER, packer.TRUE);
 422         FileOutputStream fos = null;
 423         try {
 424             // Write out to a jtreg scratch area
 425             fos = new FileOutputStream(packFile);
 426             // Call the packer
 427             packer.pack(jarFile, fos);
 428         } finally {
 429             close(fos);
 430         }
 431     }
 432 
 433     // uses java unpacker, slow but useful to discover issues with the packer
 434     static void unpackj(File inFile, JarOutputStream jarStream)
 435             throws IOException {
 436         unpack0(inFile, jarStream, true);
 437 
 438     }
 439 
 440     // uses native unpacker using the java APIs
 441     static void unpackn(File inFile, JarOutputStream jarStream)
 442             throws IOException {
 443         unpack0(inFile, jarStream, false);
 444     }
 445 
 446     // given a packed file, create the jar file in the current directory.
 447     private static void unpack0(File inFile, JarOutputStream jarStream,
 448             boolean useJavaUnpack) throws IOException {
 449         // Unpack the files
 450         Pack200.Unpacker unpacker = Pack200.newUnpacker();
 451         Map<String, String> props = unpacker.properties();
 452         if (useJavaUnpack) {
 453             props.put("com.sun.java.util.jar.pack.disable.native", "true");
 454         }
 455         // Call the unpacker
 456         unpacker.unpack(inFile, jarStream);
 457     }
 458 
 459     static byte[] getBuffer(ZipFile zf, ZipEntry ze) throws IOException {
 460         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 461         byte buf[] = new byte[8192];
 462         InputStream is = null;
 463         try {
 464             is = zf.getInputStream(ze);
 465             int n = is.read(buf);
 466             while (n > 0) {
 467                 baos.write(buf, 0, n);
 468                 n = is.read(buf);
 469             }
 470             return baos.toByteArray();
 471         } finally {
 472             close(is);
 473         }
 474     }
 475 
 476     static ArrayList<String> getZipFileEntryNames(ZipFile z) {
 477         ArrayList<String> out = new ArrayList<String>();
 478         for (ZipEntry ze : Collections.list(z.entries())) {
 479             out.add(ze.getName());
 480         }
 481         return out;
 482     }
 483     static List<String> runExec(List<String> cmdsList) {
 484         return runExec(cmdsList, null);
 485     }
 486     static List<String> runExec(List<String> cmdsList, Map<String, String> penv) {
 487         ArrayList<String> alist = new ArrayList<String>();
 488         ProcessBuilder pb =
 489                 new ProcessBuilder(cmdsList);
 490         Map<String, String> env = pb.environment();
 491         if (penv != null && !penv.isEmpty()) {
 492             env.putAll(penv);
 493         }
 494         pb.directory(new File("."));
 495         dirlist(new File("."));
 496         for (String x : cmdsList) {
 497             System.out.print(x + " ");
 498         }
 499         System.out.println("");
 500         int retval = 0;
 501         Process p = null;
 502         InputStreamReader ir = null;
 503         BufferedReader rd = null;
 504         InputStream is = null;
 505         try {
 506             pb.redirectErrorStream(true);
 507             p = pb.start();
 508             is = p.getInputStream();
 509             ir = new InputStreamReader(is);
 510             rd = new BufferedReader(ir, 8192);
 511 
 512             String in = rd.readLine();
 513             while (in != null) {
 514                 alist.add(in);
 515                 System.out.println(in);
 516                 in = rd.readLine();
 517             }
 518             retval = p.waitFor();
 519             if (retval != 0) {
 520                 throw new RuntimeException("process failed with non-zero exit");
 521             }
 522         } catch (Exception ex) {
 523             throw new RuntimeException(ex.getMessage());
 524         } finally {
 525             close(rd);
 526             close(ir);
 527             close(is);
 528             if (p != null) {
 529                 p.destroy();
 530             }
 531         }
 532         return alist;
 533     }
 534 
 535     static String getUnpack200Cmd() {
 536         return getAjavaCmd("unpack200");
 537     }
 538 
 539     static String getPack200Cmd() {
 540         return getAjavaCmd("pack200");
 541     }
 542 
 543     static String getJavaCmd() {
 544         return getAjavaCmd("java");
 545     }
 546 
 547     static String getJavacCmd() {
 548         return getAjavaCmd("javac");
 549     }
 550 
 551     static String getJarCmd() {
 552         return getAjavaCmd("jar");
 553     }
 554 
 555     static String getJimageCmd() {
 556         return getAjavaCmd("jimage");
 557     }
 558 
 559     static String getAjavaCmd(String cmdStr) {
 560         File binDir = new File(JavaHome, "bin");
 561         File unpack200File = IsWindows
 562                 ? new File(binDir, cmdStr + ".exe")
 563                 : new File(binDir, cmdStr);
 564 
 565         String cmd = unpack200File.getAbsolutePath();
 566         if (!unpack200File.canExecute()) {
 567             throw new RuntimeException("please check" +
 568                     cmd + " exists and is executable");
 569         }
 570         return cmd;
 571     }
 572 
 573     static File createRtJar() throws Exception {
 574         File rtJar = new File("rt.jar");
 575         new JrtToZip(rtJar).run();
 576         return rtJar;
 577     }
 578 
 579     /*
 580      * A helper class to create a pseudo rt.jar.
 581      */
 582     static class JrtToZip {
 583 
 584         final File outFile;
 585 
 586         public static void main(String[] args) throws Exception {
 587             new JrtToZip(new File(args[0])).run();
 588         }
 589 
 590         JrtToZip(File outFile) throws Exception {
 591             this.outFile = outFile;
 592         }
 593 
 594         void run() throws Exception {
 595             URI uri = URI.create("jar:file:///" + outFile.getAbsolutePath().replace("\\", "/"));
 596             Map<String, String> env = new HashMap<>();
 597             env.put("create", "true");
 598             try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
 599                 toZipfs(zipfs);
 600             }
 601         }
 602 
 603         void toZipfs(FileSystem zipfs) throws Exception {
 604             FileSystem jrtfs = FileSystems.getFileSystem(new URL("jrt:/").toURI());
 605             for (Path p : jrtfs.getRootDirectories()) {
 606                 Files.walkFileTree(p, new FileVisitor<Path>() {
 607                     @Override
 608                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
 609                         return FileVisitResult.CONTINUE;
 610                     }
 611 
 612                     @Override
 613                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 614                         // all we need are all the classes in the JDK
 615                         if (file.toString().endsWith(".class")) {
 616                             // System.out.println("extracting: " + file);
 617                             Path zpath = zipfs.getPath(file.toString());
 618                             Files.createDirectories(zpath.getParent());
 619                             Files.copy(file, zpath, REPLACE_EXISTING);
 620                         }
 621                         return FileVisitResult.CONTINUE;
 622                     }
 623 
 624                     @Override
 625                     public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
 626                         return FileVisitResult.CONTINUE;
 627                     }
 628 
 629                     @Override
 630                     public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
 631                         return FileVisitResult.CONTINUE;
 632                     }
 633                 });
 634             }
 635         }
 636     }
 637 }