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
26 package jdk.tools.jlink.builder;
27
28 import java.io.BufferedOutputStream;
29 import java.io.BufferedWriter;
30 import java.io.ByteArrayInputStream;
31 import java.io.DataOutputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.UncheckedIOException;
38 import java.io.OutputStream;
39 import java.io.OutputStreamWriter;
40 import java.io.Writer;
41 import java.lang.module.ModuleDescriptor;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.FileAlreadyExistsException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.nio.file.StandardOpenOption;
48 import java.nio.file.attribute.PosixFilePermission;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Optional;
56 import java.util.Properties;
57 import java.util.Set;
58 import static java.util.stream.Collectors.*;
59
60 import jdk.tools.jlink.internal.BasicImageWriter;
61 import jdk.tools.jlink.internal.plugins.FileCopierPlugin.SymImageFile;
62 import jdk.tools.jlink.internal.ExecutableImage;
63 import jdk.tools.jlink.plugin.ResourcePool;
64 import jdk.tools.jlink.plugin.ResourcePoolEntry;
65 import jdk.tools.jlink.plugin.ResourcePoolModule;
66 import jdk.tools.jlink.plugin.PluginException;
67
68 /**
69 *
70 * Default Image Builder. This builder creates the default runtime image layout.
71 */
72 public final class DefaultImageBuilder implements ImageBuilder {
73
74 /**
75 * The default java executable Image.
76 */
77 static final class DefaultExecutableImage implements ExecutableImage {
78
79 private final Path home;
80 private final List<String> args;
81 private final Set<String> modules;
82
83 DefaultExecutableImage(Path home, Set<String> modules) {
84 Objects.requireNonNull(home);
85 if (!Files.exists(home)) {
86 throw new IllegalArgumentException("Invalid image home");
87 }
88 this.home = home;
89 this.modules = Collections.unmodifiableSet(modules);
90 this.args = createArgs(home);
91 }
92
153
154 private void addModules(Properties props, Set<String> modules) throws IOException {
155 StringBuilder builder = new StringBuilder();
156 int i = 0;
157 for (String m : modules) {
158 builder.append(m);
159 if (i < modules.size() - 1) {
160 builder.append(",");
161 }
162 i++;
163 }
164 props.setProperty("MODULES", quote(builder.toString()));
165 }
166
167 @Override
168 public void storeFiles(ResourcePool files) {
169 try {
170 // populate release properties up-front. targetOsName
171 // field is assigned from there and used elsewhere.
172 Properties release = releaseProperties(files);
173 Path bin = root.resolve("bin");
174
175 // check any duplicated resource files
176 Map<Path, Set<String>> duplicates = new HashMap<>();
177 files.entries()
178 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
179 .collect(groupingBy(this::entryToImagePath,
180 mapping(ResourcePoolEntry::moduleName, toSet())))
181 .entrySet()
182 .stream()
183 .filter(e -> e.getValue().size() > 1)
184 .forEach(e -> duplicates.put(e.getKey(), e.getValue()));
185
186 // write non-classes resource files to the image
187 files.entries()
188 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
189 .forEach(f -> {
190 try {
191 accept(f);
192 } catch (FileAlreadyExistsException e) {
193 // error for duplicated entries
205 files.moduleView().modules().forEach(m -> {
206 // Only add modules that contain packages
207 if (!m.packages().isEmpty()) {
208 modules.add(m.name());
209 }
210 });
211
212 storeFiles(modules, release);
213
214 if (root.getFileSystem().supportedFileAttributeViews()
215 .contains("posix")) {
216 // launchers in the bin directory need execute permission.
217 // On Windows, "bin" also subdirectories containing jvm.dll.
218 if (Files.isDirectory(bin)) {
219 Files.find(bin, 2, (path, attrs) -> {
220 return attrs.isRegularFile() && !path.toString().endsWith(".diz");
221 }).forEach(this::setExecutable);
222 }
223
224 // jspawnhelper is in lib or lib/<arch>
225 Path lib = root.resolve("lib");
226 if (Files.isDirectory(lib)) {
227 Files.find(lib, 2, (path, attrs) -> {
228 return path.getFileName().toString().equals("jspawnhelper")
229 || path.getFileName().toString().equals("jexec");
230 }).forEach(this::setExecutable);
231 }
232 }
233
234 // If native files are stripped completely, <root>/bin dir won't exist!
235 // So, don't bother generating launcher scripts.
236 if (Files.isDirectory(bin)) {
237 prepareApplicationFiles(files, modules);
238 }
239 } catch (IOException ex) {
240 throw new PluginException(ex);
241 }
242 }
243
244 // Parse version string and return a string that includes only version part
245 // leaving "pre", "build" information. See also: java.lang.Runtime.Version.
246 private static String parseVersion(String str) {
247 return Runtime.Version.parse(str).
248 version().
249 stream().
250 map(Object::toString).
251 collect(joining("."));
314 sb.append("JLINK_VM_OPTIONS=")
315 .append("\n");
316 sb.append("DIR=`dirname $0`")
317 .append("\n");
318 sb.append("$DIR/java $JLINK_VM_OPTIONS -m ")
319 .append(module).append('/')
320 .append(mainClass.get())
321 .append(" $@\n");
322
323 try (BufferedWriter writer = Files.newBufferedWriter(cmd,
324 StandardCharsets.ISO_8859_1,
325 StandardOpenOption.CREATE_NEW)) {
326 writer.write(sb.toString());
327 }
328 if (root.resolve("bin").getFileSystem()
329 .supportedFileAttributeViews().contains("posix")) {
330 setExecutable(cmd);
331 }
332 // generate .bat file for Windows
333 if (isWindows()) {
334 Path bat = root.resolve("bin").resolve(module + ".bat");
335 sb = new StringBuilder();
336 sb.append("@echo off")
337 .append("\r\n");
338 sb.append("set JLINK_VM_OPTIONS=")
339 .append("\r\n");
340 sb.append("set DIR=%~dp0")
341 .append("\r\n");
342 sb.append("\"%DIR%\\java\" %JLINK_VM_OPTIONS% -m ")
343 .append(module).append('/')
344 .append(mainClass.get())
345 .append(" %*\r\n");
346
347 try (BufferedWriter writer = Files.newBufferedWriter(bat,
348 StandardCharsets.ISO_8859_1,
349 StandardOpenOption.CREATE_NEW)) {
350 writer.write(sb.toString());
351 }
352 }
353 }
354 }
358 public DataOutputStream getJImageOutputStream() {
359 try {
360 Path jimageFile = mdir.resolve(BasicImageWriter.MODULES_IMAGE_NAME);
361 OutputStream fos = Files.newOutputStream(jimageFile);
362 BufferedOutputStream bos = new BufferedOutputStream(fos);
363 return new DataOutputStream(bos);
364 } catch (IOException ex) {
365 throw new UncheckedIOException(ex);
366 }
367 }
368
369 /**
370 * Returns the file name of this entry
371 */
372 private String entryToFileName(ResourcePoolEntry entry) {
373 if (entry.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
374 throw new IllegalArgumentException("invalid type: " + entry);
375
376 String module = "/" + entry.moduleName() + "/";
377 String filename = entry.path().substring(module.length());
378 // Remove radical native|config|...
379 return filename.substring(filename.indexOf('/') + 1);
380 }
381
382 /**
383 * Returns the path of the given entry to be written in the image
384 */
385 private Path entryToImagePath(ResourcePoolEntry entry) {
386 switch (entry.type()) {
387 case NATIVE_LIB:
388 String filename = entryToFileName(entry);
389 return Paths.get(nativeDir(filename), filename);
390 case NATIVE_CMD:
391 return Paths.get("bin", entryToFileName(entry));
392 case CONFIG:
393 return Paths.get("conf", entryToFileName(entry));
394 case HEADER_FILE:
395 return Paths.get("include", entryToFileName(entry));
396 case MAN_PAGE:
397 return Paths.get("man", entryToFileName(entry));
398 case TOP:
399 return Paths.get(entryToFileName(entry));
400 case OTHER:
401 return Paths.get("other", entryToFileName(entry));
402 default:
403 throw new IllegalArgumentException("invalid type: " + entry);
404 }
405 }
406
407 private void accept(ResourcePoolEntry file) throws IOException {
408 try (InputStream in = file.content()) {
409 switch (file.type()) {
410 case NATIVE_LIB:
411 Path dest = root.resolve(entryToImagePath(file));
412 writeEntry(in, dest);
413 break;
414 case NATIVE_CMD:
415 Path p = root.resolve(entryToImagePath(file));
416 writeEntry(in, p);
417 p.toFile().setExecutable(true);
418 break;
419 case CONFIG:
420 writeEntry(in, root.resolve(entryToImagePath(file)));
421 break;
422 case HEADER_FILE:
423 writeEntry(in, root.resolve(entryToImagePath(file)));
424 break;
425 case MAN_PAGE:
426 writeEntry(in, root.resolve(entryToImagePath(file)));
427 break;
428 case TOP:
429 break;
430 case OTHER:
431 String filename = entryToFileName(file);
432 if (file instanceof SymImageFile) {
433 SymImageFile sym = (SymImageFile) file;
434 Path target = root.resolve(sym.getTargetPath());
435 if (!Files.exists(target)) {
436 throw new IOException("Sym link target " + target
437 + " doesn't exist");
438 }
439 writeSymEntry(root.resolve(filename), target);
440 } else {
441 writeEntry(in, root.resolve(filename));
442 }
443 break;
444 default:
445 throw new InternalError("unexpected entry: " + file.path());
446 }
447 }
448 }
449
450 private void writeEntry(InputStream in, Path dstFile) throws IOException {
451 Objects.requireNonNull(in);
452 Objects.requireNonNull(dstFile);
453 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
454 Files.copy(in, dstFile);
455 }
456
457 private void writeSymEntry(Path dstFile, Path target) throws IOException {
458 Objects.requireNonNull(dstFile);
459 Objects.requireNonNull(target);
460 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
461 Files.createLink(dstFile, target);
462 }
463
464 private String nativeDir(String filename) {
465 if (isWindows()) {
466 if (filename.endsWith(".dll") || filename.endsWith(".diz")
467 || filename.endsWith(".pdb") || filename.endsWith(".map")) {
468 return "bin";
469 } else {
470 return "lib";
471 }
472 } else {
473 return "lib";
474 }
475 }
476
477 private boolean isWindows() {
478 return targetOsName.startsWith("Windows");
479 }
480
481 /**
482 * chmod ugo+x file
483 */
484 private void setExecutable(Path file) {
485 try {
486 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
487 perms.add(PosixFilePermission.OWNER_EXECUTE);
488 perms.add(PosixFilePermission.GROUP_EXECUTE);
489 perms.add(PosixFilePermission.OTHERS_EXECUTE);
490 Files.setPosixFilePermissions(file, perms);
491 } catch (IOException ioe) {
492 throw new UncheckedIOException(ioe);
493 }
494 }
495
496 private static void createUtf8File(File file, String content) throws IOException {
497 try (OutputStream fout = new FileOutputStream(file);
498 Writer output = new OutputStreamWriter(fout, "UTF-8")) {
499 output.write(content);
500 }
501 }
502
503 @Override
504 public ExecutableImage getExecutableImage() {
505 return new DefaultExecutableImage(root, modules);
506 }
507
508 // This is experimental, we should get rid-off the scripts in a near future
509 private static void patchScripts(ExecutableImage img, List<String> args) throws IOException {
510 Objects.requireNonNull(args);
511 if (!args.isEmpty()) {
512 Files.find(img.getHome().resolve("bin"), 2, (path, attrs) -> {
513 return img.getModules().contains(path.getFileName().toString());
514 }).forEach((p) -> {
515 try {
516 String pattern = "JLINK_VM_OPTIONS=";
517 byte[] content = Files.readAllBytes(p);
518 String str = new String(content, StandardCharsets.UTF_8);
519 int index = str.indexOf(pattern);
520 StringBuilder builder = new StringBuilder();
521 if (index != -1) {
522 builder.append(str.substring(0, index)).
523 append(pattern);
524 for (String s : args) {
525 builder.append(s).append(" ");
526 }
527 String remain = str.substring(index + pattern.length());
528 builder.append(remain);
529 str = builder.toString();
530 try (BufferedWriter writer = Files.newBufferedWriter(p,
531 StandardCharsets.ISO_8859_1,
532 StandardOpenOption.WRITE)) {
533 writer.write(str);
534 }
535 }
536 } catch (IOException ex) {
537 throw new RuntimeException(ex);
538 }
539 });
540 }
541 }
542
543 public static ExecutableImage getExecutableImage(Path root) {
544 Path binDir = root.resolve("bin");
545 if (Files.exists(binDir.resolve("java")) ||
546 Files.exists(binDir.resolve("java.exe"))) {
547 return new DefaultExecutableImage(root, retrieveModules(root));
548 }
549 return null;
550 }
551
552 private static Set<String> retrieveModules(Path root) {
553 Path releaseFile = root.resolve("release");
554 Set<String> modules = new HashSet<>();
555 if (Files.exists(releaseFile)) {
556 Properties release = new Properties();
557 try (FileInputStream fi = new FileInputStream(releaseFile.toFile())) {
558 release.load(fi);
559 } catch (IOException ex) {
560 System.err.println("Can't read release file " + ex);
561 }
562 String mods = release.getProperty("MODULES");
563 if (mods != null) {
564 String[] arr = mods.split(",");
|
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
26 package jdk.tools.jlink.builder;
27
28 import java.io.BufferedOutputStream;
29 import java.io.BufferedWriter;
30 import java.io.ByteArrayInputStream;
31 import java.io.DataOutputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.io.OutputStreamWriter;
39 import java.io.UncheckedIOException;
40 import java.io.Writer;
41 import java.lang.module.ModuleDescriptor;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.FileAlreadyExistsException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.nio.file.StandardOpenOption;
48 import java.nio.file.attribute.PosixFilePermission;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Optional;
56 import java.util.Properties;
57 import java.util.Set;
58 import static java.util.stream.Collectors.*;
59
60 import jdk.tools.jlink.internal.BasicImageWriter;
61 import jdk.tools.jlink.internal.plugins.FileCopierPlugin.SymImageFile;
62 import jdk.tools.jlink.internal.ExecutableImage;
63 import jdk.tools.jlink.plugin.ResourcePool;
64 import jdk.tools.jlink.plugin.ResourcePoolEntry;
65 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
66 import jdk.tools.jlink.plugin.ResourcePoolModule;
67 import jdk.tools.jlink.plugin.PluginException;
68
69 /**
70 *
71 * Default Image Builder. This builder creates the default runtime image layout.
72 */
73 public final class DefaultImageBuilder implements ImageBuilder {
74 // Top-level directory names in a modular runtime image
75 public static final String BIN_DIRNAME = "bin";
76 public static final String CONF_DIRNAME = "conf";
77 public static final String INCLUDE_DIRNAME = "include";
78 public static final String LIB_DIRNAME = "lib";
79 public static final String LEGAL_DIRNAME = "legal";
80 public static final String MAN_DIRNAME = "man";
81
82 /**
83 * The default java executable Image.
84 */
85 static final class DefaultExecutableImage implements ExecutableImage {
86
87 private final Path home;
88 private final List<String> args;
89 private final Set<String> modules;
90
91 DefaultExecutableImage(Path home, Set<String> modules) {
92 Objects.requireNonNull(home);
93 if (!Files.exists(home)) {
94 throw new IllegalArgumentException("Invalid image home");
95 }
96 this.home = home;
97 this.modules = Collections.unmodifiableSet(modules);
98 this.args = createArgs(home);
99 }
100
161
162 private void addModules(Properties props, Set<String> modules) throws IOException {
163 StringBuilder builder = new StringBuilder();
164 int i = 0;
165 for (String m : modules) {
166 builder.append(m);
167 if (i < modules.size() - 1) {
168 builder.append(",");
169 }
170 i++;
171 }
172 props.setProperty("MODULES", quote(builder.toString()));
173 }
174
175 @Override
176 public void storeFiles(ResourcePool files) {
177 try {
178 // populate release properties up-front. targetOsName
179 // field is assigned from there and used elsewhere.
180 Properties release = releaseProperties(files);
181 Path bin = root.resolve(BIN_DIRNAME);
182
183 // check any duplicated resource files
184 Map<Path, Set<String>> duplicates = new HashMap<>();
185 files.entries()
186 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
187 .collect(groupingBy(this::entryToImagePath,
188 mapping(ResourcePoolEntry::moduleName, toSet())))
189 .entrySet()
190 .stream()
191 .filter(e -> e.getValue().size() > 1)
192 .forEach(e -> duplicates.put(e.getKey(), e.getValue()));
193
194 // write non-classes resource files to the image
195 files.entries()
196 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
197 .forEach(f -> {
198 try {
199 accept(f);
200 } catch (FileAlreadyExistsException e) {
201 // error for duplicated entries
213 files.moduleView().modules().forEach(m -> {
214 // Only add modules that contain packages
215 if (!m.packages().isEmpty()) {
216 modules.add(m.name());
217 }
218 });
219
220 storeFiles(modules, release);
221
222 if (root.getFileSystem().supportedFileAttributeViews()
223 .contains("posix")) {
224 // launchers in the bin directory need execute permission.
225 // On Windows, "bin" also subdirectories containing jvm.dll.
226 if (Files.isDirectory(bin)) {
227 Files.find(bin, 2, (path, attrs) -> {
228 return attrs.isRegularFile() && !path.toString().endsWith(".diz");
229 }).forEach(this::setExecutable);
230 }
231
232 // jspawnhelper is in lib or lib/<arch>
233 Path lib = root.resolve(LIB_DIRNAME);
234 if (Files.isDirectory(lib)) {
235 Files.find(lib, 2, (path, attrs) -> {
236 return path.getFileName().toString().equals("jspawnhelper")
237 || path.getFileName().toString().equals("jexec");
238 }).forEach(this::setExecutable);
239 }
240
241 // read-only legal notices/license files
242 Path legal = root.resolve(LEGAL_DIRNAME);
243 if (Files.isDirectory(legal)) {
244 Files.find(legal, 2, (path, attrs) -> {
245 return attrs.isRegularFile();
246 }).forEach(this::setReadOnly);
247 }
248 }
249
250 // If native files are stripped completely, <root>/bin dir won't exist!
251 // So, don't bother generating launcher scripts.
252 if (Files.isDirectory(bin)) {
253 prepareApplicationFiles(files, modules);
254 }
255 } catch (IOException ex) {
256 throw new PluginException(ex);
257 }
258 }
259
260 // Parse version string and return a string that includes only version part
261 // leaving "pre", "build" information. See also: java.lang.Runtime.Version.
262 private static String parseVersion(String str) {
263 return Runtime.Version.parse(str).
264 version().
265 stream().
266 map(Object::toString).
267 collect(joining("."));
330 sb.append("JLINK_VM_OPTIONS=")
331 .append("\n");
332 sb.append("DIR=`dirname $0`")
333 .append("\n");
334 sb.append("$DIR/java $JLINK_VM_OPTIONS -m ")
335 .append(module).append('/')
336 .append(mainClass.get())
337 .append(" $@\n");
338
339 try (BufferedWriter writer = Files.newBufferedWriter(cmd,
340 StandardCharsets.ISO_8859_1,
341 StandardOpenOption.CREATE_NEW)) {
342 writer.write(sb.toString());
343 }
344 if (root.resolve("bin").getFileSystem()
345 .supportedFileAttributeViews().contains("posix")) {
346 setExecutable(cmd);
347 }
348 // generate .bat file for Windows
349 if (isWindows()) {
350 Path bat = root.resolve(BIN_DIRNAME).resolve(module + ".bat");
351 sb = new StringBuilder();
352 sb.append("@echo off")
353 .append("\r\n");
354 sb.append("set JLINK_VM_OPTIONS=")
355 .append("\r\n");
356 sb.append("set DIR=%~dp0")
357 .append("\r\n");
358 sb.append("\"%DIR%\\java\" %JLINK_VM_OPTIONS% -m ")
359 .append(module).append('/')
360 .append(mainClass.get())
361 .append(" %*\r\n");
362
363 try (BufferedWriter writer = Files.newBufferedWriter(bat,
364 StandardCharsets.ISO_8859_1,
365 StandardOpenOption.CREATE_NEW)) {
366 writer.write(sb.toString());
367 }
368 }
369 }
370 }
374 public DataOutputStream getJImageOutputStream() {
375 try {
376 Path jimageFile = mdir.resolve(BasicImageWriter.MODULES_IMAGE_NAME);
377 OutputStream fos = Files.newOutputStream(jimageFile);
378 BufferedOutputStream bos = new BufferedOutputStream(fos);
379 return new DataOutputStream(bos);
380 } catch (IOException ex) {
381 throw new UncheckedIOException(ex);
382 }
383 }
384
385 /**
386 * Returns the file name of this entry
387 */
388 private String entryToFileName(ResourcePoolEntry entry) {
389 if (entry.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
390 throw new IllegalArgumentException("invalid type: " + entry);
391
392 String module = "/" + entry.moduleName() + "/";
393 String filename = entry.path().substring(module.length());
394
395 // Remove radical native|config|...
396 return filename.substring(filename.indexOf('/') + 1);
397 }
398
399 /**
400 * Returns the path of the given entry to be written in the image
401 */
402 private Path entryToImagePath(ResourcePoolEntry entry) {
403 switch (entry.type()) {
404 case NATIVE_LIB:
405 String filename = entryToFileName(entry);
406 return Paths.get(nativeDir(filename), filename);
407 case NATIVE_CMD:
408 return Paths.get(BIN_DIRNAME, entryToFileName(entry));
409 case CONFIG:
410 return Paths.get(CONF_DIRNAME, entryToFileName(entry));
411 case HEADER_FILE:
412 return Paths.get(INCLUDE_DIRNAME, entryToFileName(entry));
413 case MAN_PAGE:
414 return Paths.get(MAN_DIRNAME, entryToFileName(entry));
415 case LEGAL_NOTICE:
416 return Paths.get(LEGAL_DIRNAME, entryToFileName(entry));
417 case TOP:
418 return Paths.get(entryToFileName(entry));
419 case OTHER:
420 return Paths.get("other", entryToFileName(entry));
421 default:
422 throw new IllegalArgumentException("invalid type: " + entry);
423 }
424 }
425
426 private void accept(ResourcePoolEntry file) throws IOException {
427 if (file.linkedTarget() != null && file.type() != Type.LEGAL_NOTICE) {
428 throw new UnsupportedOperationException("symbolic link not implemented: " + file);
429 }
430
431 try (InputStream in = file.content()) {
432 switch (file.type()) {
433 case NATIVE_LIB:
434 Path dest = root.resolve(entryToImagePath(file));
435 writeEntry(in, dest);
436 break;
437 case NATIVE_CMD:
438 Path p = root.resolve(entryToImagePath(file));
439 writeEntry(in, p);
440 p.toFile().setExecutable(true);
441 break;
442 case CONFIG:
443 case HEADER_FILE:
444 case MAN_PAGE:
445 writeEntry(in, root.resolve(entryToImagePath(file)));
446 break;
447 case LEGAL_NOTICE:
448 Path source = entryToImagePath(file);
449 if (file.linkedTarget() == null) {
450 writeEntry(in, root.resolve(source));
451 } else {
452 Path target = entryToImagePath(file.linkedTarget());
453 Path relPath = source.getParent().relativize(target);
454 writeSymLinkEntry(root.resolve(source), relPath);
455 }
456 break;
457 case TOP:
458 break;
459 case OTHER:
460 String filename = entryToFileName(file);
461 if (file instanceof SymImageFile) {
462 SymImageFile sym = (SymImageFile) file;
463 Path target = root.resolve(sym.getTargetPath());
464 if (!Files.exists(target)) {
465 throw new IOException("Sym link target " + target
466 + " doesn't exist");
467 }
468 writeSymEntry(root.resolve(filename), target);
469 } else {
470 writeEntry(in, root.resolve(filename));
471 }
472 break;
473 default:
474 throw new InternalError("unexpected entry: " + file.path());
475 }
476 }
477 }
478
479 private void writeEntry(InputStream in, Path dstFile) throws IOException {
480 Objects.requireNonNull(in);
481 Objects.requireNonNull(dstFile);
482 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
483 Files.copy(in, dstFile);
484 }
485
486 private void writeSymEntry(Path dstFile, Path target) throws IOException {
487 Objects.requireNonNull(dstFile);
488 Objects.requireNonNull(target);
489 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
490 Files.createLink(dstFile, target);
491 }
492
493 /*
494 * Create a symbolic link to the given target if the target platform
495 * supports symbolic link; otherwise, it will create a tiny file
496 * to contain the path to the target.
497 */
498 private void writeSymLinkEntry(Path dstFile, Path target) throws IOException {
499 Objects.requireNonNull(dstFile);
500 Objects.requireNonNull(target);
501 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
502 if (!isWindows() && root.getFileSystem()
503 .supportedFileAttributeViews()
504 .contains("posix")) {
505 Files.createSymbolicLink(dstFile, target);
506 } else {
507 try (BufferedWriter writer = Files.newBufferedWriter(dstFile)) {
508 writer.write(String.format("Please see %s%n", target.toString()));
509 }
510 }
511 }
512
513 private String nativeDir(String filename) {
514 if (isWindows()) {
515 if (filename.endsWith(".dll") || filename.endsWith(".diz")
516 || filename.endsWith(".pdb") || filename.endsWith(".map")) {
517 return BIN_DIRNAME;
518 } else {
519 return LIB_DIRNAME;
520 }
521 } else {
522 return LIB_DIRNAME;
523 }
524 }
525
526 private boolean isWindows() {
527 return targetOsName.startsWith("Windows");
528 }
529
530 /**
531 * chmod ugo+x file
532 */
533 private void setExecutable(Path file) {
534 try {
535 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
536 perms.add(PosixFilePermission.OWNER_EXECUTE);
537 perms.add(PosixFilePermission.GROUP_EXECUTE);
538 perms.add(PosixFilePermission.OTHERS_EXECUTE);
539 Files.setPosixFilePermissions(file, perms);
540 } catch (IOException ioe) {
541 throw new UncheckedIOException(ioe);
542 }
543 }
544
545 /**
546 * chmod ugo-w file
547 */
548 private void setReadOnly(Path file) {
549 try {
550 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
551 perms.remove(PosixFilePermission.OWNER_WRITE);
552 perms.remove(PosixFilePermission.GROUP_WRITE);
553 perms.remove(PosixFilePermission.OTHERS_WRITE);
554 Files.setPosixFilePermissions(file, perms);
555 } catch (IOException ioe) {
556 throw new UncheckedIOException(ioe);
557 }
558 }
559
560 private static void createUtf8File(File file, String content) throws IOException {
561 try (OutputStream fout = new FileOutputStream(file);
562 Writer output = new OutputStreamWriter(fout, "UTF-8")) {
563 output.write(content);
564 }
565 }
566
567 @Override
568 public ExecutableImage getExecutableImage() {
569 return new DefaultExecutableImage(root, modules);
570 }
571
572 // This is experimental, we should get rid-off the scripts in a near future
573 private static void patchScripts(ExecutableImage img, List<String> args) throws IOException {
574 Objects.requireNonNull(args);
575 if (!args.isEmpty()) {
576 Files.find(img.getHome().resolve(BIN_DIRNAME), 2, (path, attrs) -> {
577 return img.getModules().contains(path.getFileName().toString());
578 }).forEach((p) -> {
579 try {
580 String pattern = "JLINK_VM_OPTIONS=";
581 byte[] content = Files.readAllBytes(p);
582 String str = new String(content, StandardCharsets.UTF_8);
583 int index = str.indexOf(pattern);
584 StringBuilder builder = new StringBuilder();
585 if (index != -1) {
586 builder.append(str.substring(0, index)).
587 append(pattern);
588 for (String s : args) {
589 builder.append(s).append(" ");
590 }
591 String remain = str.substring(index + pattern.length());
592 builder.append(remain);
593 str = builder.toString();
594 try (BufferedWriter writer = Files.newBufferedWriter(p,
595 StandardCharsets.ISO_8859_1,
596 StandardOpenOption.WRITE)) {
597 writer.write(str);
598 }
599 }
600 } catch (IOException ex) {
601 throw new RuntimeException(ex);
602 }
603 });
604 }
605 }
606
607 public static ExecutableImage getExecutableImage(Path root) {
608 Path binDir = root.resolve(BIN_DIRNAME);
609 if (Files.exists(binDir.resolve("java")) ||
610 Files.exists(binDir.resolve("java.exe"))) {
611 return new DefaultExecutableImage(root, retrieveModules(root));
612 }
613 return null;
614 }
615
616 private static Set<String> retrieveModules(Path root) {
617 Path releaseFile = root.resolve("release");
618 Set<String> modules = new HashSet<>();
619 if (Files.exists(releaseFile)) {
620 Properties release = new Properties();
621 try (FileInputStream fi = new FileInputStream(releaseFile.toFile())) {
622 release.load(fi);
623 } catch (IOException ex) {
624 System.err.println("Can't read release file " + ex);
625 }
626 String mods = release.getProperty("MODULES");
627 if (mods != null) {
628 String[] arr = mods.split(",");
|