mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8353185: Introduce the concept of upgradeable files in context of JEP 493
Reviewed-by: clanger, ihse, alanb
This commit is contained in:
parent
d7676c39b6
commit
4e24dc003c
32
make/modules/jdk.jlink/Java.gmk
Normal file
32
make/modules/jdk.jlink/Java.gmk
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
#
|
||||||
|
# This code is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 2 only, as
|
||||||
|
# published by the Free Software Foundation. Oracle designates this
|
||||||
|
# particular file as subject to the "Classpath" exception as provided
|
||||||
|
# by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
#
|
||||||
|
# This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
# version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
# accompanied this code).
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License version
|
||||||
|
# 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
# or visit www.oracle.com if you need additional information or have any
|
||||||
|
# questions.
|
||||||
|
#
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Instruct SetupJavaCompilation for the jdk.jlink module to include
|
||||||
|
# upgrade_files_<module-name>.conf files
|
||||||
|
COPY += .conf
|
||||||
|
|
||||||
|
################################################################################
|
||||||
@ -45,6 +45,7 @@ import java.util.HexFormat;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -76,6 +77,7 @@ public class JRTArchive implements Archive {
|
|||||||
private final Map<String, ResourceDiff> resDiff;
|
private final Map<String, ResourceDiff> resDiff;
|
||||||
private final boolean errorOnModifiedFile;
|
private final boolean errorOnModifiedFile;
|
||||||
private final TaskHelper taskHelper;
|
private final TaskHelper taskHelper;
|
||||||
|
private final Set<String> upgradeableFiles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JRTArchive constructor
|
* JRTArchive constructor
|
||||||
@ -86,12 +88,15 @@ public class JRTArchive implements Archive {
|
|||||||
* install aborts the link.
|
* install aborts the link.
|
||||||
* @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
|
* @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
|
||||||
* possibly an empty list if there are no differences.
|
* possibly an empty list if there are no differences.
|
||||||
|
* @param taskHelper The task helper instance.
|
||||||
|
* @param upgradeableFiles The set of files that are allowed for upgrades.
|
||||||
*/
|
*/
|
||||||
JRTArchive(String module,
|
JRTArchive(String module,
|
||||||
Path path,
|
Path path,
|
||||||
boolean errorOnModifiedFile,
|
boolean errorOnModifiedFile,
|
||||||
List<ResourceDiff> perModDiff,
|
List<ResourceDiff> perModDiff,
|
||||||
TaskHelper taskHelper) {
|
TaskHelper taskHelper,
|
||||||
|
Set<String> upgradeableFiles) {
|
||||||
this.module = module;
|
this.module = module;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.ref = ModuleFinder.ofSystem()
|
this.ref = ModuleFinder.ofSystem()
|
||||||
@ -105,6 +110,7 @@ public class JRTArchive implements Archive {
|
|||||||
this.resDiff = Objects.requireNonNull(perModDiff).stream()
|
this.resDiff = Objects.requireNonNull(perModDiff).stream()
|
||||||
.collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
|
.collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
|
||||||
this.taskHelper = taskHelper;
|
this.taskHelper = taskHelper;
|
||||||
|
this.upgradeableFiles = upgradeableFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,7 +223,8 @@ public class JRTArchive implements Archive {
|
|||||||
|
|
||||||
// Read from the base JDK image.
|
// Read from the base JDK image.
|
||||||
Path path = BASE.resolve(m.resPath);
|
Path path = BASE.resolve(m.resPath);
|
||||||
if (shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
|
if (!isUpgradeableFile(m.resPath) &&
|
||||||
|
shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
|
||||||
if (errorOnModifiedFile) {
|
if (errorOnModifiedFile) {
|
||||||
String msg = taskHelper.getMessage("err.runtime.link.modified.file", path.toString());
|
String msg = taskHelper.getMessage("err.runtime.link.modified.file", path.toString());
|
||||||
IOException cause = new IOException(msg);
|
IOException cause = new IOException(msg);
|
||||||
@ -239,6 +246,17 @@ public class JRTArchive implements Archive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certain files in a module are considered upgradeable. That is,
|
||||||
|
* their hash sums aren't checked.
|
||||||
|
*
|
||||||
|
* @param resPath The resource path of the file to check for upgradeability.
|
||||||
|
* @return {@code true} if the file is upgradeable. {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
private boolean isUpgradeableFile(String resPath) {
|
||||||
|
return upgradeableFiles.contains(resPath);
|
||||||
|
}
|
||||||
|
|
||||||
static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
|
static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
|
||||||
if (isSymlink) {
|
if (isSymlink) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -28,7 +28,10 @@ package jdk.tools.jlink.internal;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
|
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
|
||||||
|
|
||||||
@ -42,6 +45,9 @@ public class LinkableRuntimeImage {
|
|||||||
public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
|
public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
|
||||||
// The diff files per module for supporting linking from the run-time image
|
// The diff files per module for supporting linking from the run-time image
|
||||||
public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s";
|
public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s";
|
||||||
|
// meta data for upgradable files
|
||||||
|
private static final String UPGRADEABLE_FILES_PATTERN = "jdk/tools/jlink/internal/runtimelink/upgrade_files_%s.conf";
|
||||||
|
private static final Module JDK_JLINK_MOD = LinkableRuntimeImage.class.getModule();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to be able to show whether or not a runtime is capable of
|
* In order to be able to show whether or not a runtime is capable of
|
||||||
@ -62,7 +68,38 @@ public class LinkableRuntimeImage {
|
|||||||
|
|
||||||
private static InputStream getDiffInputStream(String module) throws IOException {
|
private static InputStream getDiffInputStream(String module) throws IOException {
|
||||||
String resourceName = String.format(DIFF_PATTERN, module);
|
String resourceName = String.format(DIFF_PATTERN, module);
|
||||||
return LinkableRuntimeImage.class.getModule().getResourceAsStream(resourceName);
|
return JDK_JLINK_MOD.getResourceAsStream(resourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> upgradeableFiles(String module) {
|
||||||
|
String resourceName = String.format(UPGRADEABLE_FILES_PATTERN, module);
|
||||||
|
InputStream filesIn = null;
|
||||||
|
try {
|
||||||
|
filesIn = JDK_JLINK_MOD.getResourceAsStream(resourceName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("Unexpected IO error getting res stream");
|
||||||
|
}
|
||||||
|
if (filesIn == null) {
|
||||||
|
// no upgradeable files
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
Set<String> upgradeableFiles = new HashSet<>();
|
||||||
|
final InputStream in = filesIn;
|
||||||
|
try (in;
|
||||||
|
Scanner scanner = new Scanner(filesIn)) {
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
String line = scanner.nextLine();
|
||||||
|
if (line.trim().startsWith("#")) {
|
||||||
|
// Skip comments
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
upgradeableFiles.add(scanner.nextLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("Failure to retrieve upgradeable files for " +
|
||||||
|
"module " + module, e);
|
||||||
|
}
|
||||||
|
return upgradeableFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Archive newArchive(String module,
|
public static Archive newArchive(String module,
|
||||||
@ -81,7 +118,13 @@ public class LinkableRuntimeImage {
|
|||||||
throw new AssertionError("Failure to retrieve resource diff for " +
|
throw new AssertionError("Failure to retrieve resource diff for " +
|
||||||
"module " + module, e);
|
"module " + module, e);
|
||||||
}
|
}
|
||||||
return new JRTArchive(module, path, !ignoreModifiedRuntime, perModuleDiff, taskHelper);
|
Set<String> upgradeableFiles = upgradeableFiles(module);
|
||||||
|
return new JRTArchive(module,
|
||||||
|
path,
|
||||||
|
!ignoreModifiedRuntime,
|
||||||
|
perModuleDiff,
|
||||||
|
taskHelper,
|
||||||
|
upgradeableFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
# Configuration for resource paths of files allowed to be
|
||||||
|
# upgraded (in java.base)
|
||||||
|
lib/tzdb.dat
|
||||||
|
lib/security/cacerts
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Red Hat, Inc.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import tests.Helper;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Verify that no errors are reported for files that have been
|
||||||
|
* upgraded when linking from the run-time image
|
||||||
|
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
|
||||||
|
* @library ../../lib /test/lib
|
||||||
|
* @modules java.base/jdk.internal.jimage
|
||||||
|
* jdk.jlink/jdk.tools.jlink.internal
|
||||||
|
* jdk.jlink/jdk.tools.jlink.plugin
|
||||||
|
* jdk.jlink/jdk.tools.jimage
|
||||||
|
* @build tests.* jdk.test.lib.process.OutputAnalyzer
|
||||||
|
* jdk.test.lib.process.ProcessTools
|
||||||
|
* @run main/othervm -Xmx1g UpgradeableFileCacertsTest
|
||||||
|
*/
|
||||||
|
public class UpgradeableFileCacertsTest extends ModifiedFilesTest {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generated with:
|
||||||
|
* $ rm -f server.keystore && keytool -genkey -alias jlink-upgrade-test \
|
||||||
|
* -keyalg RSA -dname CN=jlink-upgrade-test \
|
||||||
|
* -storepass changeit -keysize 3072 -sigalg SHA512withRSA \
|
||||||
|
* -validity 7300 -keystore server.keystore
|
||||||
|
* $ keytool -export -alias jlink-upgrade-test -storepass changeit \
|
||||||
|
* -keystore server.keystore -rfc
|
||||||
|
*/
|
||||||
|
private static final String CERT = """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID3jCCAkagAwIBAgIJALiT/+HXBkSIMA0GCSqGSIb3DQEBDQUAMB0xGzAZBgNV
|
||||||
|
BAMTEmpsaW5rLXVwZ3JhZGUtdGVzdDAeFw0yNTA0MDQxMjA3MjJaFw00NTAzMzAx
|
||||||
|
MjA3MjJaMB0xGzAZBgNVBAMTEmpsaW5rLXVwZ3JhZGUtdGVzdDCCAaIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggGPADCCAYoCggGBANmrnCDKqSXEJRIiSi4yHWN97ILls3RqYjED
|
||||||
|
la3AZTeXnZrrEIgSjVFUMxCztYqbWoVzKa2lov42Vue2BXVYffcQ8TKc2EJDNO+2
|
||||||
|
uRKQZpsN7RI4QoVBR2Rq8emrO8CrdOQT7Hh4agxkN9AOvGKMFdt+fXeCIPIuflKP
|
||||||
|
f+RfvhLfC2A70Y+Uu74C5uWgLloA/HF0SsVxf9KmqS9fZBQaiTYhKyoDghCRlWpa
|
||||||
|
nPIHB1XVaRdw8aSpCuzIOQzSCTTlLcammJkBjbFwMZdQG7eglTWzIYryZwe/cyY2
|
||||||
|
xctLVW3xhUHvnMFG+MajeFny2mxNu163Rxf/rBu4e7jRC/LGSU784nJGapq5K170
|
||||||
|
WbaeceKp+YORJBviFFORrmkPIwIgE+iGCD6PD6Xwu8vcpeuTVDgsSWMlfgCL3NoI
|
||||||
|
GXmdGiI2Xc/hQX7uzu3UBF6IcPDMTcYr2JKYbgu3v2/vDlJu3qO2ycUeePo5jhuG
|
||||||
|
X2WgcHkb6uOU4W5qdbCA+wFPVZBuwQIDAQABoyEwHzAdBgNVHQ4EFgQUtMJM0+ct
|
||||||
|
ssKqryRckk4YEWdYAZkwDQYJKoZIhvcNAQENBQADggGBAI8A6gJQ8wDx12sy2ZI4
|
||||||
|
1q9b+WG6w3LcFEF6Fko5NBizhtfmVycQv4mBa/NJgx4DZmd+5d60gJcTp/hJXGY0
|
||||||
|
LZyFilm/AgxsLNUUQLbHAV6TWqd3ODWwswAuew9sFU6izl286a9W65tbMWL5r1EA
|
||||||
|
t34ZYVWZYbCS9+czU98WomH4uarRAOlzcEUui3ZX6ZcQxWbz/R2wtKcUPUAYnsqH
|
||||||
|
JPivpE25G5xW2Dp/yeQTrlffq9OLgZWVz0jtOguBUMnsUsgCcpQZtqZX08//wtpz
|
||||||
|
ohLHFGvpXTPbRumRasWWtnRR/QqGRT66tYDqybXXz37UtKZ8VKW0sv2ypVbmAEs5
|
||||||
|
pLkA/3XiXlstJuCD6cW0Gfbpb5rrPPD46O3FDVlmqlTH3b/MsiQREdydqGzqY7uG
|
||||||
|
AA2GFVaKFASA5ls01CfHLAcrKxSVixditXvsjeIqhddB7Pnbsx20RdzPQoeo9/hF
|
||||||
|
WeIrh4zePDPZChuLR8ZyxeVJhLB71nTrTDDjwXarVez9Xw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
""";
|
||||||
|
|
||||||
|
private static final String CERT_ALIAS = "jlink-upgrade-test";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
UpgradeableFileCacertsTest test = new UpgradeableFileCacertsTest();
|
||||||
|
test.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String initialImageName() {
|
||||||
|
return "java-base-jlink-upgrade-cacerts";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception {
|
||||||
|
CapturingHandler handler = new CapturingHandler();
|
||||||
|
jlinkUsingImage(new JlinkSpecBuilder()
|
||||||
|
.helper(helper)
|
||||||
|
.imagePath(initialImage)
|
||||||
|
.name("java-base-jlink-upgrade-cacerts-target")
|
||||||
|
.addModule("java.base")
|
||||||
|
.validatingModule("java.base")
|
||||||
|
.build(), handler);
|
||||||
|
OutputAnalyzer analyzer = handler.analyzer();
|
||||||
|
// verify we don't get any modified warning
|
||||||
|
analyzer.stdoutShouldNotContain(modifiedFile.toString() + " has been modified");
|
||||||
|
analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException");
|
||||||
|
analyzer.stdoutShouldNotContain("IOException");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an extra certificate in the cacerts file so that it no longer matches
|
||||||
|
// the recorded hash sum at build time.
|
||||||
|
protected Path modifyFileInImage(Path jmodLessImg)
|
||||||
|
throws IOException, AssertionError {
|
||||||
|
Path cacerts = jmodLessImg.resolve(Path.of("lib", "security", "cacerts"));
|
||||||
|
try (FileInputStream fin = new FileInputStream(cacerts.toFile())) {
|
||||||
|
KeyStore certStore = KeyStore.getInstance(cacerts.toFile(),
|
||||||
|
(char[])null);
|
||||||
|
certStore.load(fin, (char[])null);
|
||||||
|
X509Certificate cert;
|
||||||
|
try (ByteArrayInputStream bin = new ByteArrayInputStream(CERT.getBytes())) {
|
||||||
|
cert = (X509Certificate)generateCertificate(bin);
|
||||||
|
} catch (ClassCastException | CertificateException ce) {
|
||||||
|
throw new AssertionError("Test failed unexpectedly", ce);
|
||||||
|
}
|
||||||
|
certStore.setCertificateEntry(CERT_ALIAS, cert);
|
||||||
|
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||||
|
certStore.store(bout, (char[])null);
|
||||||
|
try (FileOutputStream fout = new FileOutputStream(cacerts.toFile())) {
|
||||||
|
fout.write(bout.toByteArray());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError("Test failed unexpectedly: ", e);
|
||||||
|
}
|
||||||
|
return cacerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Certificate generateCertificate(InputStream in)
|
||||||
|
throws CertificateException, IOException {
|
||||||
|
byte[] data = in.readAllBytes();
|
||||||
|
return CertificateFactory.getInstance("X.509")
|
||||||
|
.generateCertificate(new ByteArrayInputStream(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user