Skip to content

Commit db68a0c

Browse files
Andrew Leonard
committed
8276766: Enable jar and jmod to produce deterministic timestamped content
Reviewed-by: ihse, lancea, alanb, jgneff
1 parent 6eb6ec0 commit db68a0c

File tree

Image for: File tree

8 files changed

Image for: 8 files changed
+480
-22
lines changed

8 files changed

Image for: 8 files changed
+480
-22
lines changed

‎src/jdk.jartool/share/classes/sun/tools/jar/GNUStyleOptions.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,21 @@
3434
import java.util.regex.PatternSyntaxException;
3535
import jdk.internal.module.ModulePath;
3636
import jdk.internal.module.ModuleResolution;
37+
import java.time.ZoneOffset;
38+
import java.time.ZonedDateTime;
39+
import java.time.LocalDateTime;
40+
import java.time.format.DateTimeFormatter;
41+
import java.time.format.DateTimeParseException;
3742

3843
/**
3944
* Parser for GNU Style Options.
4045
*/
4146
class GNUStyleOptions {
4247

48+
// Valid --date range
49+
static final ZonedDateTime DATE_MIN = ZonedDateTime.parse("1980-01-01T00:00:02Z");
50+
static final ZonedDateTime DATE_MAX = ZonedDateTime.parse("2099-12-31T23:59:59Z");
51+
4352
static class BadArgs extends Exception {
4453
static final long serialVersionUID = 0L;
4554

@@ -188,6 +197,20 @@ void process(Main jartool, String opt, String arg) {
188197
jartool.flag0 = true;
189198
}
190199
},
200+
new Option(true, OptionType.CREATE_UPDATE_INDEX, "--date") {
201+
void process(Main jartool, String opt, String arg) throws BadArgs {
202+
try {
203+
ZonedDateTime date = ZonedDateTime.parse(arg, DateTimeFormatter.ISO_ZONED_DATE_TIME)
204+
.withZoneSameInstant(ZoneOffset.UTC);
205+
if (date.isBefore(DATE_MIN) || date.isAfter(DATE_MAX)) {
206+
throw new BadArgs("error.date.out.of.range", arg);
207+
}
208+
jartool.date = date.toLocalDateTime();
209+
} catch (DateTimeParseException x) {
210+
throw new BadArgs("error.date.notvalid", arg);
211+
}
212+
}
213+
},
191214

192215
// Hidden options
193216
new Option(false, OptionType.OTHER, "-P") {

‎src/jdk.jartool/share/classes/sun/tools/jar/Main.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.util.zip.ZipFile;
6161
import java.util.zip.ZipInputStream;
6262
import java.util.zip.ZipOutputStream;
63+
import java.util.concurrent.TimeUnit;
6364
import jdk.internal.module.Checks;
6465
import jdk.internal.module.ModuleHashes;
6566
import jdk.internal.module.ModuleHashesBuilder;
@@ -68,6 +69,8 @@
6869
import jdk.internal.module.ModuleResolution;
6970
import jdk.internal.module.ModuleTarget;
7071
import jdk.internal.util.jar.JarIndex;
72+
import java.time.LocalDateTime;
73+
import java.time.ZoneOffset;
7174

7275
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
7376
import static java.util.jar.JarFile.MANIFEST_NAME;
@@ -174,6 +177,9 @@ public int hashCode() {
174177
static final int VERSIONS_DIR_LENGTH = VERSIONS_DIR.length();
175178
private static ResourceBundle rsrc;
176179

180+
/* Date option for entry timestamps resolved to UTC Local time */
181+
LocalDateTime date;
182+
177183
/**
178184
* If true, maintain compatibility with JDK releases prior to 6.0 by
179185
* timestamping extracted files with the time at which they are extracted.
@@ -862,12 +868,12 @@ void create(OutputStream out, Manifest manifest) throws IOException
862868
output(getMsg("out.added.manifest"));
863869
}
864870
ZipEntry e = new ZipEntry(MANIFEST_DIR);
865-
e.setTime(System.currentTimeMillis());
871+
setZipEntryTime(e);
866872
e.setSize(0);
867873
e.setCrc(0);
868874
zos.putNextEntry(e);
869875
e = new ZipEntry(MANIFEST_NAME);
870-
e.setTime(System.currentTimeMillis());
876+
setZipEntryTime(e);
871877
if (flag0) {
872878
crc32Manifest(e, manifest);
873879
}
@@ -967,7 +973,7 @@ boolean update(InputStream in, OutputStream out,
967973
// do our own compression
968974
ZipEntry e2 = new ZipEntry(name);
969975
e2.setMethod(e.getMethod());
970-
e2.setTime(e.getTime());
976+
setZipEntryTime(e2, e.getTime());
971977
e2.setComment(e.getComment());
972978
e2.setExtra(e.getExtra());
973979
if (e.getMethod() == ZipEntry.STORED) {
@@ -1033,7 +1039,7 @@ private void addIndex(JarIndex index, ZipOutputStream zos)
10331039
throws IOException
10341040
{
10351041
ZipEntry e = new ZipEntry(INDEX_NAME);
1036-
e.setTime(System.currentTimeMillis());
1042+
setZipEntryTime(e);
10371043
if (flag0) {
10381044
CRC32OutputStream os = new CRC32OutputStream();
10391045
index.write(os);
@@ -1055,9 +1061,9 @@ private void updateModuleInfo(Map<String, ModuleInfoEntry> moduleInfos, ZipOutpu
10551061
ZipEntry e = new ZipEntry(name);
10561062
FileTime lastModified = mie.getLastModifiedTime();
10571063
if (lastModified != null) {
1058-
e.setLastModifiedTime(lastModified);
1064+
setZipEntryTime(e, lastModified.toMillis());
10591065
} else {
1060-
e.setLastModifiedTime(FileTime.fromMillis(System.currentTimeMillis()));
1066+
setZipEntryTime(e);
10611067
}
10621068
if (flag0) {
10631069
crc32ModuleInfo(e, bytes);
@@ -1083,7 +1089,7 @@ private boolean updateManifest(Manifest m, ZipOutputStream zos)
10831089
addMultiRelease(m);
10841090
}
10851091
ZipEntry e = new ZipEntry(MANIFEST_NAME);
1086-
e.setTime(System.currentTimeMillis());
1092+
setZipEntryTime(e);
10871093
if (flag0) {
10881094
crc32Manifest(e, m);
10891095
}
@@ -1204,7 +1210,7 @@ void addFile(ZipOutputStream zos, Entry entry) throws IOException {
12041210
out.print(formatMsg("out.adding", name));
12051211
}
12061212
ZipEntry e = new ZipEntry(name);
1207-
e.setTime(file.lastModified());
1213+
setZipEntryTime(e, file.lastModified());
12081214
if (size == 0) {
12091215
e.setMethod(ZipEntry.STORED);
12101216
e.setSize(0);
@@ -2318,4 +2324,18 @@ ModuleHashes computeHashes(String name) {
23182324
static Comparator<ZipEntry> ENTRY_COMPARATOR =
23192325
Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
23202326

2327+
// Set the ZipEntry dostime using date if specified otherwise the current time
2328+
private void setZipEntryTime(ZipEntry e) {
2329+
setZipEntryTime(e, System.currentTimeMillis());
2330+
}
2331+
2332+
// Set the ZipEntry dostime using the date if specified
2333+
// otherwise the original time
2334+
private void setZipEntryTime(ZipEntry e, long origTime) {
2335+
if (date != null) {
2336+
e.setTimeLocal(date);
2337+
} else {
2338+
e.setTime(origTime);
2339+
}
2340+
}
23212341
}

‎src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
33
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
#
55
# This code is free software; you can redistribute it and/or modify it
@@ -82,6 +82,10 @@ error.release.value.toosmall=\
8282
release {0} not valid, must be >= 9
8383
error.release.unexpected.versioned.entry=\
8484
unexpected versioned entry {0} for release {1}
85+
error.date.notvalid=\
86+
date {0} is not a valid ISO-8601 extended offset date-time with optional time-zone
87+
error.date.out.of.range=\
88+
date {0} is not within the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z
8589
error.validator.jarfile.exception=\
8690
can not validate {0}: {1}
8791
error.validator.jarfile.invalid=\
@@ -290,6 +294,10 @@ main.help.opt.create.update.index=\
290294
\ Operation modifiers valid only in create, update, and generate-index mode:\n
291295
main.help.opt.create.update.index.no-compress=\
292296
\ -0, --no-compress Store only; use no ZIP compression
297+
main.help.opt.create.update.index.date=\
298+
\ --date=TIMESTAMP The timestamp in ISO-8601 extended offset date-time with\n\
299+
\ optional time-zone format, to use for the timestamps of\n\
300+
\ entries, e.g. "2022-02-12T12:30:00-05:00"
293301
main.help.opt.other=\
294302
\ Other options:\n
295303
main.help.opt.other.help=\

‎src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -41,6 +41,7 @@
4141
import java.util.zip.ZipEntry;
4242
import java.util.zip.ZipOutputStream;
4343
import jdk.internal.jmod.JmodFile;
44+
import java.time.LocalDateTime;
4445

4546
import static jdk.internal.jmod.JmodFile.*;
4647

@@ -54,15 +55,17 @@ class JmodOutputStream extends OutputStream implements AutoCloseable {
5455
* This method creates (or overrides, if exists) the JMOD file,
5556
* returning the the output stream to write to the JMOD file.
5657
*/
57-
static JmodOutputStream newOutputStream(Path file) throws IOException {
58+
static JmodOutputStream newOutputStream(Path file, LocalDateTime date) throws IOException {
5859
OutputStream out = Files.newOutputStream(file);
5960
BufferedOutputStream bos = new BufferedOutputStream(out);
60-
return new JmodOutputStream(bos);
61+
return new JmodOutputStream(bos, date);
6162
}
6263

6364
private final ZipOutputStream zos;
64-
private JmodOutputStream(OutputStream out) {
65+
private final LocalDateTime date;
66+
private JmodOutputStream(OutputStream out, LocalDateTime date) {
6567
this.zos = new ZipOutputStream(out);
68+
this.date = date;
6669
try {
6770
JmodFile.writeMagicNumber(out);
6871
} catch (IOException e) {
@@ -104,7 +107,11 @@ public void writeEntry(InputStream in, Entry e) throws IOException {
104107
// sun.tools.jar.Main.update()
105108
ZipEntry e2 = new ZipEntry(e1.getName());
106109
e2.setMethod(e1.getMethod());
107-
e2.setTime(e1.getTime());
110+
if (date != null) {
111+
e2.setTimeLocal(date);
112+
} else {
113+
e2.setTime(e1.getTime());
114+
}
108115
e2.setComment(e1.getComment());
109116
e2.setExtra(e1.getExtra());
110117
if (e1.getMethod() == ZipEntry.STORED) {
@@ -124,7 +131,11 @@ private ZipEntry newEntry(Section section, String path) throws IOException {
124131
String name = Paths.get(prefix, path).toString()
125132
.replace(File.separatorChar, '/');
126133
entries.get(section).add(path);
127-
return new ZipEntry(name);
134+
ZipEntry zipEntry = new ZipEntry(name);
135+
if (date != null) {
136+
zipEntry.setTimeLocal(date);
137+
}
138+
return zipEntry;
128139
}
129140

130141
public boolean contains(Section section, String path) {

‎src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@
6262
import java.util.zip.ZipEntry;
6363
import java.util.zip.ZipException;
6464
import java.util.zip.ZipFile;
65+
import java.time.LocalDateTime;
66+
import java.time.ZonedDateTime;
67+
import java.time.ZoneOffset;
68+
import java.time.format.DateTimeFormatter;
69+
import java.time.format.DateTimeParseException;
6570

6671
import jdk.internal.jmod.JmodFile;
6772
import jdk.internal.jmod.JmodFile.Section;
@@ -160,8 +165,13 @@ static class Options {
160165
boolean dryrun;
161166
List<PathMatcher> excludes;
162167
Path extractDir;
168+
LocalDateTime date;
163169
}
164170

171+
// Valid --date range
172+
static final ZonedDateTime DATE_MIN = ZonedDateTime.parse("1980-01-01T00:00:02Z");
173+
static final ZonedDateTime DATE_MAX = ZonedDateTime.parse("2099-12-31T23:59:59Z");
174+
165175
public int run(String[] args) {
166176

167177
try {
@@ -427,7 +437,7 @@ private boolean create() throws IOException {
427437
Path target = options.jmodFile;
428438
Path tempTarget = jmodTempFilePath(target);
429439
try {
430-
try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
440+
try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget, options.date)) {
431441
jmod.write(jos);
432442
}
433443
Files.move(tempTarget, target);
@@ -984,7 +994,11 @@ private void updateModularJar(Path target, Path tempTarget,
984994
if (e.getName().equals(MODULE_INFO)) {
985995
// what about module-info.class in versioned entries?
986996
ZipEntry ze = new ZipEntry(e.getName());
987-
ze.setTime(System.currentTimeMillis());
997+
if (options.date != null) {
998+
ze.setTimeLocal(options.date);
999+
} else {
1000+
ze.setTime(System.currentTimeMillis());
1001+
}
9881002
jos.putNextEntry(ze);
9891003
recordHashes(in, jos, moduleHashes);
9901004
jos.closeEntry();
@@ -1012,7 +1026,7 @@ private void updateJmodFile(Path target, Path tempTarget,
10121026
{
10131027

10141028
try (JmodFile jf = new JmodFile(target);
1015-
JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget))
1029+
JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget, options.date))
10161030
{
10171031
jf.stream().forEach(e -> {
10181032
try (InputStream in = jf.getInputStream(e.section(), e.name())) {
@@ -1147,6 +1161,26 @@ public Version convert(String value) {
11471161
@Override public String valuePattern() { return "module-version"; }
11481162
}
11491163

1164+
static class DateConverter implements ValueConverter<LocalDateTime> {
1165+
@Override
1166+
public LocalDateTime convert(String value) {
1167+
try {
1168+
ZonedDateTime date = ZonedDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME)
1169+
.withZoneSameInstant(ZoneOffset.UTC);
1170+
if (date.isBefore(DATE_MIN) || date.isAfter(DATE_MAX)) {
1171+
throw new CommandException("err.date.out.of.range", value);
1172+
}
1173+
return date.toLocalDateTime();
1174+
} catch (DateTimeParseException x) {
1175+
throw new CommandException("err.invalid.date", value, x.getMessage());
1176+
}
1177+
}
1178+
1179+
@Override public Class<LocalDateTime> valueType() { return LocalDateTime.class; }
1180+
1181+
@Override public String valuePattern() { return "date"; }
1182+
}
1183+
11501184
static class WarnIfResolvedReasonConverter
11511185
implements ValueConverter<ModuleResolution>
11521186
{
@@ -1382,6 +1416,11 @@ private void handleOptions(String[] args) {
13821416
OptionSpec<Void> version
13831417
= parser.accepts("version", getMessage("main.opt.version"));
13841418

1419+
OptionSpec<LocalDateTime> date
1420+
= parser.accepts("date", getMessage("main.opt.date"))
1421+
.withRequiredArg()
1422+
.withValuesConvertedBy(new DateConverter());
1423+
13851424
NonOptionArgumentSpec<String> nonOptions
13861425
= parser.nonOptions();
13871426

@@ -1425,6 +1464,8 @@ private void handleOptions(String[] args) {
14251464
options.manPages = getLastElement(opts.valuesOf(manPages));
14261465
if (opts.has(legalNotices))
14271466
options.legalNotices = getLastElement(opts.valuesOf(legalNotices));
1467+
if (opts.has(date))
1468+
options.date = opts.valueOf(date);
14281469
if (opts.has(modulePath)) {
14291470
Path[] dirs = getLastElement(opts.valuesOf(modulePath)).toArray(new Path[0]);
14301471
options.moduleFinder = ModulePath.of(Runtime.version(), true, dirs);

‎src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
2+
# Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
33
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
#
55
# This code is free software; you can redistribute it and/or modify it
@@ -74,6 +74,9 @@ main.opt.hash-modules=Compute and record hashes to tie a packaged module\
7474
main.opt.do-not-resolve-by-default=Exclude from the default root set of modules
7575
main.opt.warn-if-resolved=Hint for a tool to issue a warning if the module \
7676
is resolved. One of deprecated, deprecated-for-removal, or incubating
77+
main.opt.date=Date and time for the timestamps of entries, specified in ISO-8601\
78+
\ extended offset date-time with optional time-zone format, e.g.\
79+
\ "2022-02-12T12:30:00-05:00"
7780

7881
main.opt.cmdfile=Read options from the specified file
7982

@@ -106,6 +109,8 @@ err.module.descriptor.not.found=Module descriptor not found
106109
err.missing.export.or.open.packages=Packages that are exported or open in {0} are not present: {1}
107110
err.module.resolution.fail=Resolution failed: {0}
108111
err.no.moduleToHash=No hashes recorded: no module matching {0} found to record hashes
112+
err.invalid.date=--date {0} is not a valid ISO-8601 extended offset date-time with optional time-zone format: {1}
113+
err.date.out.of.range=--date {0} is out of the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z
109114
warn.invalid.arg=Invalid classname or pathname not exist: {0}
110115
warn.no.module.hashes=No hashes recorded: no module specified for hashing depends on {0}
111116
warn.ignore.entry=ignoring entry {0}, in section {1}

0 commit comments

Image for: 0 commit comments
Comments
 (0)