Skip to content

Commit 4e24dc0

Browse files
committed
8353185: Introduce the concept of upgradeable files in context of JEP 493
Reviewed-by: clanger, ihse, alanb
1 parent d7676c3 commit 4e24dc0

File tree

5 files changed

+254
-4
lines changed

5 files changed

+254
-4
lines changed

make/modules/jdk.jlink/Java.gmk

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# Copyright (c) 2025, 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+
26+
################################################################################
27+
28+
# Instruct SetupJavaCompilation for the jdk.jlink module to include
29+
# upgrade_files_<module-name>.conf files
30+
COPY += .conf
31+
32+
################################################################################

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.List;
4646
import java.util.Map;
4747
import java.util.Objects;
48+
import java.util.Set;
4849
import java.util.function.Function;
4950
import java.util.function.Predicate;
5051
import java.util.stream.Collectors;
@@ -76,6 +77,7 @@ public class JRTArchive implements Archive {
7677
private final Map<String, ResourceDiff> resDiff;
7778
private final boolean errorOnModifiedFile;
7879
private final TaskHelper taskHelper;
80+
private final Set<String> upgradeableFiles;
7981

8082
/**
8183
* JRTArchive constructor
@@ -86,12 +88,15 @@ public class JRTArchive implements Archive {
8688
* install aborts the link.
8789
* @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
8890
* possibly an empty list if there are no differences.
91+
* @param taskHelper The task helper instance.
92+
* @param upgradeableFiles The set of files that are allowed for upgrades.
8993
*/
9094
JRTArchive(String module,
9195
Path path,
9296
boolean errorOnModifiedFile,
9397
List<ResourceDiff> perModDiff,
94-
TaskHelper taskHelper) {
98+
TaskHelper taskHelper,
99+
Set<String> upgradeableFiles) {
95100
this.module = module;
96101
this.path = path;
97102
this.ref = ModuleFinder.ofSystem()
@@ -105,6 +110,7 @@ public class JRTArchive implements Archive {
105110
this.resDiff = Objects.requireNonNull(perModDiff).stream()
106111
.collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
107112
this.taskHelper = taskHelper;
113+
this.upgradeableFiles = upgradeableFiles;
108114
}
109115

110116
@Override
@@ -217,7 +223,8 @@ private void addNonClassResources() {
217223

218224
// Read from the base JDK image.
219225
Path path = BASE.resolve(m.resPath);
220-
if (shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
226+
if (!isUpgradeableFile(m.resPath) &&
227+
shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
221228
if (errorOnModifiedFile) {
222229
String msg = taskHelper.getMessage("err.runtime.link.modified.file", path.toString());
223230
IOException cause = new IOException(msg);
@@ -239,6 +246,17 @@ private void addNonClassResources() {
239246
}
240247
}
241248

249+
/**
250+
* Certain files in a module are considered upgradeable. That is,
251+
* their hash sums aren't checked.
252+
*
253+
* @param resPath The resource path of the file to check for upgradeability.
254+
* @return {@code true} if the file is upgradeable. {@code false} otherwise.
255+
*/
256+
private boolean isUpgradeableFile(String resPath) {
257+
return upgradeableFiles.contains(resPath);
258+
}
259+
242260
static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
243261
if (isSymlink) {
244262
return false;

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java

+45-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
import java.io.IOException;
2929
import java.io.InputStream;
3030
import java.nio.file.Path;
31+
import java.util.HashSet;
3132
import java.util.List;
33+
import java.util.Scanner;
34+
import java.util.Set;
3235

3336
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
3437

@@ -42,6 +45,9 @@ public class LinkableRuntimeImage {
4245
public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
4346
// The diff files per module for supporting linking from the run-time image
4447
public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s";
48+
// meta data for upgradable files
49+
private static final String UPGRADEABLE_FILES_PATTERN = "jdk/tools/jlink/internal/runtimelink/upgrade_files_%s.conf";
50+
private static final Module JDK_JLINK_MOD = LinkableRuntimeImage.class.getModule();
4551

4652
/**
4753
* In order to be able to show whether or not a runtime is capable of
@@ -62,7 +68,38 @@ public static boolean isLinkableRuntime() {
6268

6369
private static InputStream getDiffInputStream(String module) throws IOException {
6470
String resourceName = String.format(DIFF_PATTERN, module);
65-
return LinkableRuntimeImage.class.getModule().getResourceAsStream(resourceName);
71+
return JDK_JLINK_MOD.getResourceAsStream(resourceName);
72+
}
73+
74+
private static Set<String> upgradeableFiles(String module) {
75+
String resourceName = String.format(UPGRADEABLE_FILES_PATTERN, module);
76+
InputStream filesIn = null;
77+
try {
78+
filesIn = JDK_JLINK_MOD.getResourceAsStream(resourceName);
79+
} catch (IOException e) {
80+
throw new AssertionError("Unexpected IO error getting res stream");
81+
}
82+
if (filesIn == null) {
83+
// no upgradeable files
84+
return Set.of();
85+
}
86+
Set<String> upgradeableFiles = new HashSet<>();
87+
final InputStream in = filesIn;
88+
try (in;
89+
Scanner scanner = new Scanner(filesIn)) {
90+
while (scanner.hasNextLine()) {
91+
String line = scanner.nextLine();
92+
if (line.trim().startsWith("#")) {
93+
// Skip comments
94+
continue;
95+
}
96+
upgradeableFiles.add(scanner.nextLine());
97+
}
98+
} catch (IOException e) {
99+
throw new AssertionError("Failure to retrieve upgradeable files for " +
100+
"module " + module, e);
101+
}
102+
return upgradeableFiles;
66103
}
67104

68105
public static Archive newArchive(String module,
@@ -81,7 +118,13 @@ public static Archive newArchive(String module,
81118
throw new AssertionError("Failure to retrieve resource diff for " +
82119
"module " + module, e);
83120
}
84-
return new JRTArchive(module, path, !ignoreModifiedRuntime, perModuleDiff, taskHelper);
121+
Set<String> upgradeableFiles = upgradeableFiles(module);
122+
return new JRTArchive(module,
123+
path,
124+
!ignoreModifiedRuntime,
125+
perModuleDiff,
126+
taskHelper,
127+
upgradeableFiles);
85128
}
86129

87130

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Configuration for resource paths of files allowed to be
2+
# upgraded (in java.base)
3+
lib/tzdb.dat
4+
lib/security/cacerts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright (c) 2025, Red Hat, Inc.
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.io.ByteArrayInputStream;
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.FileInputStream;
27+
import java.io.FileOutputStream;
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.nio.file.Path;
31+
import java.security.KeyStore;
32+
import java.security.cert.Certificate;
33+
import java.security.cert.CertificateException;
34+
import java.security.cert.CertificateFactory;
35+
import java.security.cert.X509Certificate;
36+
37+
import jdk.test.lib.process.OutputAnalyzer;
38+
import tests.Helper;
39+
40+
/*
41+
* @test
42+
* @summary Verify that no errors are reported for files that have been
43+
* upgraded when linking from the run-time image
44+
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
45+
* @library ../../lib /test/lib
46+
* @modules java.base/jdk.internal.jimage
47+
* jdk.jlink/jdk.tools.jlink.internal
48+
* jdk.jlink/jdk.tools.jlink.plugin
49+
* jdk.jlink/jdk.tools.jimage
50+
* @build tests.* jdk.test.lib.process.OutputAnalyzer
51+
* jdk.test.lib.process.ProcessTools
52+
* @run main/othervm -Xmx1g UpgradeableFileCacertsTest
53+
*/
54+
public class UpgradeableFileCacertsTest extends ModifiedFilesTest {
55+
56+
/*
57+
* Generated with:
58+
* $ rm -f server.keystore && keytool -genkey -alias jlink-upgrade-test \
59+
* -keyalg RSA -dname CN=jlink-upgrade-test \
60+
* -storepass changeit -keysize 3072 -sigalg SHA512withRSA \
61+
* -validity 7300 -keystore server.keystore
62+
* $ keytool -export -alias jlink-upgrade-test -storepass changeit \
63+
* -keystore server.keystore -rfc
64+
*/
65+
private static final String CERT = """
66+
-----BEGIN CERTIFICATE-----
67+
MIID3jCCAkagAwIBAgIJALiT/+HXBkSIMA0GCSqGSIb3DQEBDQUAMB0xGzAZBgNV
68+
BAMTEmpsaW5rLXVwZ3JhZGUtdGVzdDAeFw0yNTA0MDQxMjA3MjJaFw00NTAzMzAx
69+
MjA3MjJaMB0xGzAZBgNVBAMTEmpsaW5rLXVwZ3JhZGUtdGVzdDCCAaIwDQYJKoZI
70+
hvcNAQEBBQADggGPADCCAYoCggGBANmrnCDKqSXEJRIiSi4yHWN97ILls3RqYjED
71+
la3AZTeXnZrrEIgSjVFUMxCztYqbWoVzKa2lov42Vue2BXVYffcQ8TKc2EJDNO+2
72+
uRKQZpsN7RI4QoVBR2Rq8emrO8CrdOQT7Hh4agxkN9AOvGKMFdt+fXeCIPIuflKP
73+
f+RfvhLfC2A70Y+Uu74C5uWgLloA/HF0SsVxf9KmqS9fZBQaiTYhKyoDghCRlWpa
74+
nPIHB1XVaRdw8aSpCuzIOQzSCTTlLcammJkBjbFwMZdQG7eglTWzIYryZwe/cyY2
75+
xctLVW3xhUHvnMFG+MajeFny2mxNu163Rxf/rBu4e7jRC/LGSU784nJGapq5K170
76+
WbaeceKp+YORJBviFFORrmkPIwIgE+iGCD6PD6Xwu8vcpeuTVDgsSWMlfgCL3NoI
77+
GXmdGiI2Xc/hQX7uzu3UBF6IcPDMTcYr2JKYbgu3v2/vDlJu3qO2ycUeePo5jhuG
78+
X2WgcHkb6uOU4W5qdbCA+wFPVZBuwQIDAQABoyEwHzAdBgNVHQ4EFgQUtMJM0+ct
79+
ssKqryRckk4YEWdYAZkwDQYJKoZIhvcNAQENBQADggGBAI8A6gJQ8wDx12sy2ZI4
80+
1q9b+WG6w3LcFEF6Fko5NBizhtfmVycQv4mBa/NJgx4DZmd+5d60gJcTp/hJXGY0
81+
LZyFilm/AgxsLNUUQLbHAV6TWqd3ODWwswAuew9sFU6izl286a9W65tbMWL5r1EA
82+
t34ZYVWZYbCS9+czU98WomH4uarRAOlzcEUui3ZX6ZcQxWbz/R2wtKcUPUAYnsqH
83+
JPivpE25G5xW2Dp/yeQTrlffq9OLgZWVz0jtOguBUMnsUsgCcpQZtqZX08//wtpz
84+
ohLHFGvpXTPbRumRasWWtnRR/QqGRT66tYDqybXXz37UtKZ8VKW0sv2ypVbmAEs5
85+
pLkA/3XiXlstJuCD6cW0Gfbpb5rrPPD46O3FDVlmqlTH3b/MsiQREdydqGzqY7uG
86+
AA2GFVaKFASA5ls01CfHLAcrKxSVixditXvsjeIqhddB7Pnbsx20RdzPQoeo9/hF
87+
WeIrh4zePDPZChuLR8ZyxeVJhLB71nTrTDDjwXarVez9Xw==
88+
-----END CERTIFICATE-----
89+
""";
90+
91+
private static final String CERT_ALIAS = "jlink-upgrade-test";
92+
93+
public static void main(String[] args) throws Exception {
94+
UpgradeableFileCacertsTest test = new UpgradeableFileCacertsTest();
95+
test.run();
96+
}
97+
98+
@Override
99+
String initialImageName() {
100+
return "java-base-jlink-upgrade-cacerts";
101+
}
102+
103+
@Override
104+
void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception {
105+
CapturingHandler handler = new CapturingHandler();
106+
jlinkUsingImage(new JlinkSpecBuilder()
107+
.helper(helper)
108+
.imagePath(initialImage)
109+
.name("java-base-jlink-upgrade-cacerts-target")
110+
.addModule("java.base")
111+
.validatingModule("java.base")
112+
.build(), handler);
113+
OutputAnalyzer analyzer = handler.analyzer();
114+
// verify we don't get any modified warning
115+
analyzer.stdoutShouldNotContain(modifiedFile.toString() + " has been modified");
116+
analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException");
117+
analyzer.stdoutShouldNotContain("IOException");
118+
}
119+
120+
// Add an extra certificate in the cacerts file so that it no longer matches
121+
// the recorded hash sum at build time.
122+
protected Path modifyFileInImage(Path jmodLessImg)
123+
throws IOException, AssertionError {
124+
Path cacerts = jmodLessImg.resolve(Path.of("lib", "security", "cacerts"));
125+
try (FileInputStream fin = new FileInputStream(cacerts.toFile())) {
126+
KeyStore certStore = KeyStore.getInstance(cacerts.toFile(),
127+
(char[])null);
128+
certStore.load(fin, (char[])null);
129+
X509Certificate cert;
130+
try (ByteArrayInputStream bin = new ByteArrayInputStream(CERT.getBytes())) {
131+
cert = (X509Certificate)generateCertificate(bin);
132+
} catch (ClassCastException | CertificateException ce) {
133+
throw new AssertionError("Test failed unexpectedly", ce);
134+
}
135+
certStore.setCertificateEntry(CERT_ALIAS, cert);
136+
ByteArrayOutputStream bout = new ByteArrayOutputStream();
137+
certStore.store(bout, (char[])null);
138+
try (FileOutputStream fout = new FileOutputStream(cacerts.toFile())) {
139+
fout.write(bout.toByteArray());
140+
}
141+
} catch (Exception e) {
142+
throw new AssertionError("Test failed unexpectedly: ", e);
143+
}
144+
return cacerts;
145+
}
146+
147+
private Certificate generateCertificate(InputStream in)
148+
throws CertificateException, IOException {
149+
byte[] data = in.readAllBytes();
150+
return CertificateFactory.getInstance("X.509")
151+
.generateCertificate(new ByteArrayInputStream(data));
152+
}
153+
}

0 commit comments

Comments
 (0)