JImageTask.java revision 13901:b2a69d66dc65
1/*
2 * Copyright (c) 2014, 2015, 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
26package jdk.tools.jimage;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.PrintWriter;
31import java.nio.ByteBuffer;
32import java.nio.ByteOrder;
33import java.nio.channels.FileChannel;
34import java.nio.file.Files;
35import java.nio.file.Path;
36import static java.nio.file.StandardOpenOption.READ;
37import static java.nio.file.StandardOpenOption.WRITE;
38import java.util.LinkedList;
39import java.util.List;
40import jdk.internal.jimage.BasicImageReader;
41import jdk.internal.jimage.ImageHeader;
42import static jdk.internal.jimage.ImageHeader.MAGIC;
43import static jdk.internal.jimage.ImageHeader.MAJOR_VERSION;
44import static jdk.internal.jimage.ImageHeader.MINOR_VERSION;
45import jdk.internal.jimage.ImageLocation;
46import jdk.tools.jlink.internal.ImageResourcesTree;
47import jdk.tools.jlink.internal.ImagePluginConfiguration;
48import jdk.tools.jlink.internal.ImagePluginStack;
49import jdk.tools.jlink.internal.TaskHelper;
50import jdk.tools.jlink.internal.TaskHelper.BadArgs;
51import static jdk.tools.jlink.internal.TaskHelper.JIMAGE_BUNDLE;
52import jdk.tools.jlink.internal.TaskHelper.Option;
53import jdk.tools.jlink.internal.TaskHelper.OptionsHelper;
54
55class JImageTask {
56
57    static final Option<?>[] recognizedOptions = {
58        new Option<JImageTask>(true, (task, opt, arg) -> {
59            task.options.directory = arg;
60        }, "--dir"),
61        new Option<JImageTask>(false, (task, opt, arg) -> {
62            task.options.fullVersion = true;
63        }, true, "--fullversion"),
64        new Option<JImageTask>(false, (task, opt, arg) -> {
65            task.options.help = true;
66        }, "--help"),
67        new Option<JImageTask>(true, (task, opt, arg) -> {
68            task.options.flags = arg;
69        }, "--flags"),
70        new Option<JImageTask>(false, (task, opt, arg) -> {
71            task.options.verbose = true;
72        }, "--verbose"),
73        new Option<JImageTask>(false, (task, opt, arg) -> {
74            task.options.version = true;
75        }, "--version")
76    };
77    private static final TaskHelper taskHelper
78            = new TaskHelper(JIMAGE_BUNDLE);
79    private static final OptionsHelper<JImageTask> optionsHelper
80            = taskHelper.newOptionsHelper(JImageTask.class, recognizedOptions);
81
82    static class OptionsValues {
83        Task task = Task.LIST;
84        String directory = ".";
85        boolean fullVersion;
86        boolean help;
87        String flags;
88        boolean verbose;
89        boolean version;
90        List<File> jimages = new LinkedList<>();
91    }
92
93    private static final String PROGNAME = "jimage";
94    private final OptionsValues options = new OptionsValues();
95
96    enum Task {
97        EXTRACT,
98        INFO,
99        LIST,
100        RECREATE,
101        SET,
102        VERIFY
103    };
104
105    private String pad(String string, int width, boolean justifyRight) {
106        int length = string.length();
107
108        if (length == width) {
109            return string;
110        }
111
112        if (length > width) {
113            return string.substring(0, width);
114        }
115
116        int padding = width - length;
117
118        StringBuilder sb = new StringBuilder(width);
119        if (justifyRight) {
120            for (int i = 0; i < padding; i++) {
121                sb.append(' ');
122            }
123        }
124
125        sb.append(string);
126
127        if (!justifyRight) {
128            for (int i = 0; i < padding; i++) {
129                sb.append(' ');
130            }
131        }
132
133        return sb.toString();
134    }
135
136    private String pad(String string, int width) {
137        return pad(string, width, false);
138    }
139
140    private String pad(long value, int width) {
141        return pad(Long.toString(value), width, true);
142    }
143
144    private static final int EXIT_OK = 0;        // No errors.
145    private static final int EXIT_ERROR = 1;     // Completed but reported errors.
146    private static final int EXIT_CMDERR = 2;    // Bad command-line arguments and/or switches.
147    private static final int EXIT_SYSERR = 3;    // System error or resource exhaustion.
148    private static final int EXIT_ABNORMAL = 4;  // Terminated abnormally.
149
150    int run(String[] args) {
151        if (log == null) {
152            setLog(new PrintWriter(System.out));
153        }
154
155        try {
156            List<String> unhandled = optionsHelper.handleOptions(this, args);
157            if(!unhandled.isEmpty()) {
158                options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase());
159                for(int i = 1; i < unhandled.size(); i++) {
160                    options.jimages.add(new File(unhandled.get(i)));
161                }
162            }
163            if (options.help) {
164                optionsHelper.showHelp(PROGNAME);
165            }
166            if(optionsHelper.listPlugins()) {
167                optionsHelper.listPlugins(true);
168                return EXIT_OK;
169            }
170            if (options.version || options.fullVersion) {
171                taskHelper.showVersion(options.fullVersion);
172            }
173            boolean ok = run();
174            return ok ? EXIT_OK : EXIT_ERROR;
175        } catch (BadArgs e) {
176            taskHelper.reportError(e.key, e.args);
177            if (e.showUsage) {
178                log.println(taskHelper.getMessage("main.usage.summary", PROGNAME));
179            }
180            return EXIT_CMDERR;
181        } catch (Exception x) {
182            x.printStackTrace();
183            return EXIT_ABNORMAL;
184        } finally {
185            log.flush();
186        }
187    }
188
189    private void recreate() throws Exception, BadArgs {
190        File directory = new File(options.directory);
191        if (!directory.isDirectory()) {
192            throw taskHelper.newBadArgs("err.not.a.dir", directory.getAbsolutePath());
193        }
194        Path dirPath = directory.toPath();
195        if (options.jimages.isEmpty()) {
196            throw taskHelper.newBadArgs("err.jimage.not.specified");
197        } else if (options.jimages.size() != 1) {
198            throw taskHelper.newBadArgs("err.only.one.jimage");
199        }
200
201        Path jimage = options.jimages.get(0).toPath();
202
203        if (jimage.toFile().createNewFile()) {
204            ImagePluginStack pc = ImagePluginConfiguration.parseConfiguration(taskHelper.
205                    getPluginsConfig(null, false));
206            ExtractedImage img = new ExtractedImage(dirPath, pc, log, options.verbose);
207            img.recreateJImage(jimage);
208        } else {
209            throw taskHelper.newBadArgs("err.jimage.already.exists", jimage.getFileName());
210        }
211    }
212
213    private void title(File file, BasicImageReader reader) {
214        log.println("jimage: " + file.getName());
215    }
216
217    private void listTitle(File file, BasicImageReader reader) {
218        title(file, reader);
219
220        if (options.verbose) {
221            log.print(pad("Offset", OFFSET_WIDTH + 1));
222            log.print(pad("Size", SIZE_WIDTH + 1));
223            log.print(pad("Compressed", COMPRESSEDSIZE_WIDTH + 1));
224            log.println(" Entry");
225        }
226    }
227
228    private interface JImageAction {
229        public void apply(File file, BasicImageReader reader) throws IOException, BadArgs;
230    }
231
232    private interface ResourceAction {
233        public void apply(BasicImageReader reader, String name,
234                ImageLocation location) throws IOException, BadArgs;
235    }
236
237    private void extract(BasicImageReader reader, String name,
238            ImageLocation location) throws IOException, BadArgs {
239        File directory = new File(options.directory);
240        byte[] bytes = reader.getResource(location);
241        File resource =  new File(directory, name);
242        File parent = resource.getParentFile();
243
244        if (parent.exists()) {
245            if (!parent.isDirectory()) {
246                throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
247            }
248        } else if (!parent.mkdirs()) {
249            throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
250        }
251
252        if (!ImageResourcesTree.isTreeInfoResource(name)) {
253            Files.write(resource.toPath(), bytes);
254        }
255    }
256
257    private static final int NUMBER_WIDTH = 12;
258    private static final int OFFSET_WIDTH = NUMBER_WIDTH;
259    private static final int SIZE_WIDTH = NUMBER_WIDTH;
260    private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH;
261
262    private void print(String entry, ImageLocation location) {
263        log.print(pad(location.getContentOffset(), OFFSET_WIDTH) + " ");
264        log.print(pad(location.getUncompressedSize(), SIZE_WIDTH) + " ");
265        log.print(pad(location.getCompressedSize(), COMPRESSEDSIZE_WIDTH) + " ");
266        log.println(entry);
267    }
268
269    private void print(BasicImageReader reader, String entry) {
270        if (options.verbose) {
271            print(entry, reader.findLocation(entry));
272        } else {
273            log.println(entry);
274        }
275    }
276
277    private void info(File file, BasicImageReader reader) throws IOException {
278        ImageHeader header = reader.getHeader();
279
280        log.println(" Major Version:  " + header.getMajorVersion());
281        log.println(" Minor Version:  " + header.getMinorVersion());
282        log.println(" Flags:          " + Integer.toHexString(header.getMinorVersion()));
283        log.println(" Resource Count: " + header.getResourceCount());
284        log.println(" Table Length:   " + header.getTableLength());
285        log.println(" Offsets Size:   " + header.getOffsetsSize());
286        log.println(" Redirects Size: " + header.getRedirectSize());
287        log.println(" Locations Size: " + header.getLocationsSize());
288        log.println(" Strings Size:   " + header.getStringsSize());
289        log.println(" Index Size:     " + header.getIndexSize());
290    }
291
292    private void list(BasicImageReader reader, String name, ImageLocation location) {
293        print(reader, name);
294    }
295
296    void set(File file, BasicImageReader reader) throws BadArgs {
297        try {
298            ImageHeader oldHeader = reader.getHeader();
299
300            int value = 0;
301            try {
302                value = Integer.valueOf(options.flags);
303            } catch (NumberFormatException ex) {
304                throw taskHelper.newBadArgs("err.flags.not.int", options.flags);
305            }
306
307            ImageHeader newHeader = new ImageHeader(MAGIC, MAJOR_VERSION, MINOR_VERSION,
308                    value,
309                    oldHeader.getResourceCount(), oldHeader.getTableLength(),
310                    oldHeader.getLocationsSize(), oldHeader.getStringsSize());
311
312            ByteBuffer buffer = ByteBuffer.allocate(ImageHeader.getHeaderSize());
313            buffer.order(ByteOrder.nativeOrder());
314            newHeader.writeTo(buffer);
315            buffer.rewind();
316
317            try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE)) {
318                channel.write(buffer, 0);
319            }
320        } catch (IOException ex) {
321            throw taskHelper.newBadArgs("err.cannot.update.file", file.getName());
322        }
323    }
324
325     void verify(BasicImageReader reader, String name, ImageLocation location) {
326        if (name.endsWith(".class")) {
327            byte[] bytes = reader.getResource(location);
328
329            if (bytes == null || bytes.length <= 4 ||
330                (bytes[0] & 0xFF) != 0xCA ||
331                (bytes[1] & 0xFF) != 0xFE ||
332                (bytes[2] & 0xFF) != 0xBA ||
333                (bytes[3] & 0xFF) != 0xBE) {
334                log.print(" NOT A CLASS: ");
335                print(reader, name);
336            }
337        }
338    }
339
340    private void iterate(JImageAction jimageAction,
341            ResourceAction resourceAction) throws IOException, BadArgs {
342        for (File file : options.jimages) {
343            if (!file.exists() || !file.isFile()) {
344                throw taskHelper.newBadArgs("err.not.a.jimage", file.getName());
345            }
346
347            try (BasicImageReader reader = BasicImageReader.open(file.toPath())) {
348                if (jimageAction != null) {
349                    jimageAction.apply(file, reader);
350                }
351
352                if (resourceAction != null) {
353                    String[] entryNames = reader.getEntryNames();
354
355                    for (String name : entryNames) {
356                        if (!ImageResourcesTree.isTreeInfoResource(name)) {
357                            ImageLocation location = reader.findLocation(name);
358                            resourceAction.apply(reader, name, location);
359                        }
360                    }
361                }
362            }
363        }
364    }
365
366    private boolean run() throws Exception, BadArgs {
367        switch (options.task) {
368            case EXTRACT:
369                iterate(null, this::extract);
370                break;
371            case INFO:
372                iterate(this::info, null);
373                break;
374            case LIST:
375                iterate(this::listTitle, this::list);
376                break;
377            case RECREATE:
378                recreate();
379                break;
380            case SET:
381                iterate(this::set, null);
382                break;
383            case VERIFY:
384                iterate(this::title, this::verify);
385                break;
386            default:
387                throw taskHelper.newBadArgs("err.invalid.task", options.task.name()).showUsage(true);
388        }
389        return true;
390    }
391
392    private PrintWriter log;
393    void setLog(PrintWriter out) {
394        log = out;
395        taskHelper.setLog(log);
396    }
397}
398