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