1 /*
2  * Copyright (c) 2006, 2016, 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.
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 /**
25  * @test
26  * @bug 4225317 6969651
27  * @modules jdk.jartool
28  * @summary Check extracted files have date as per those in the .jar file
29  */
30 
31 import java.io.File;
32 import java.io.PrintWriter;
33 import java.nio.file.attribute.FileTime;
34 import java.util.Date;
35 import java.util.TimeZone;
36 import java.util.spi.ToolProvider;
37 
38 public class JarEntryTime {
39     static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
40         .orElseThrow(() ->
41             new RuntimeException("jar tool not found")
42         );
43 
44 
45     // ZipEntry's mod date has 2 seconds precision: give extra time to
46     // allow for e.g. rounding/truncation and networked/samba drives.
47     static final long PRECISION = 10000L;
48 
49     static final TimeZone TZ = TimeZone.getDefault();
50     static final boolean DST = TZ.inDaylightTime(new Date());
51 
cleanup(File dir)52     static boolean cleanup(File dir) throws Throwable {
53         boolean rc = true;
54         File[] x = dir.listFiles();
55         if (x != null) {
56             for (int i = 0; i < x.length; i++) {
57                 rc &= x[i].delete();
58             }
59         }
60         return rc & dir.delete();
61     }
62 
extractJar(File jarFile, boolean useExtractionTime)63     static void extractJar(File jarFile, boolean useExtractionTime) throws Throwable {
64         String javahome = System.getProperty("java.home");
65         String jarcmd = javahome + File.separator + "bin" + File.separator + "jar";
66         String[] args;
67         if (useExtractionTime) {
68             args = new String[] {
69                 jarcmd,
70                 "-J-Dsun.tools.jar.useExtractionTime=true",
71                 "xf",
72                 jarFile.getName() };
73         } else {
74             args = new String[] {
75                 jarcmd,
76                 "xf",
77                 jarFile.getName() };
78         }
79         Process p = Runtime.getRuntime().exec(args);
80         check(p != null && (p.waitFor() == 0));
81     }
82 
realMain(String[] args)83     public static void realMain(String[] args) throws Throwable {
84 
85         File dirOuter = new File("outer");
86         File dirInner = new File(dirOuter, "inner");
87         File jarFile = new File("JarEntryTime.jar");
88         File testFile = new File("JarEntryTimeTest.txt");
89 
90         // Remove any leftovers from prior run
91         cleanup(dirInner);
92         cleanup(dirOuter);
93         jarFile.delete();
94         testFile.delete();
95 
96         /* Create a directory structure
97          * outer/
98          *     inner/
99          *         foo.txt
100          * Set the lastModified dates so that outer is created now, inner
101          * yesterday, and foo.txt created "earlier".
102          */
103         check(dirOuter.mkdir());
104         check(dirInner.mkdir());
105         File fileInner = new File(dirInner, "foo.txt");
106         try (PrintWriter pw = new PrintWriter(fileInner)) {
107             pw.println("hello, world");
108         }
109 
110         // Get the "now" from the "last-modified-time" of the last file we
111         // just created, instead of the "System.currentTimeMillis()", to
112         // workaround the possible "time difference" due to nfs.
113         final long now = fileInner.lastModified();
114         final long earlier = now - (60L * 60L * 6L * 1000L);
115         final long yesterday = now - (60L * 60L * 24L * 1000L);
116 
117         check(dirOuter.setLastModified(now));
118         check(dirInner.setLastModified(yesterday));
119         check(fileInner.setLastModified(earlier));
120 
121         // Make a jar file from that directory structure
122         check(JAR_TOOL.run(System.out, System.err,
123                            "cf", jarFile.getName(), dirOuter.getName()) == 0);
124         check(jarFile.exists());
125 
126         check(cleanup(dirInner));
127         check(cleanup(dirOuter));
128 
129         // Extract and check that the last modified values are those specified
130         // in the archive
131         extractJar(jarFile, false);
132         check(dirOuter.exists());
133         check(dirInner.exists());
134         check(fileInner.exists());
135         checkFileTime(dirOuter.lastModified(), now);
136         checkFileTime(dirInner.lastModified(), yesterday);
137         checkFileTime(fileInner.lastModified(), earlier);
138 
139         check(cleanup(dirInner));
140         check(cleanup(dirOuter));
141 
142         try (PrintWriter pw = new PrintWriter(testFile)) {
143             pw.println("hello, world");
144         }
145         final long start = testFile.lastModified();
146 
147         // Extract and check the last modified values are the current times.
148         extractJar(jarFile, true);
149 
150         try (PrintWriter pw = new PrintWriter(testFile)) {
151             pw.println("hello, world");
152         }
153         final long end = testFile.lastModified();
154 
155         check(dirOuter.exists());
156         check(dirInner.exists());
157         check(fileInner.exists());
158         checkFileTime(start, dirOuter.lastModified(), end);
159         checkFileTime(start, dirInner.lastModified(), end);
160         checkFileTime(start, fileInner.lastModified(), end);
161 
162         check(cleanup(dirInner));
163         check(cleanup(dirOuter));
164 
165         check(jarFile.delete());
166         check(testFile.delete());
167     }
168 
checkFileTime(long now, long original)169     static void checkFileTime(long now, long original) {
170         if (isTimeSettingChanged()) {
171             return;
172         }
173 
174         if (Math.abs(now - original) > PRECISION) {
175             System.out.format("Extracted to %s, expected to be close to %s%n",
176                 FileTime.fromMillis(now), FileTime.fromMillis(original));
177             fail();
178         }
179     }
180 
checkFileTime(long start, long now, long end)181     static void checkFileTime(long start, long now, long end) {
182         if (isTimeSettingChanged()) {
183             return;
184         }
185 
186         if (now < start || now > end) {
187             System.out.format("Extracted to %s, "
188                               + "expected to be after %s and before %s%n",
189                               FileTime.fromMillis(now),
190                               FileTime.fromMillis(start),
191                               FileTime.fromMillis(end));
192             fail();
193         }
194     }
195 
isTimeSettingChanged()196     private static boolean isTimeSettingChanged() {
197         TimeZone currentTZ = TimeZone.getDefault();
198         boolean currentDST = currentTZ.inDaylightTime(new Date());
199         return (!currentTZ.equals(TZ) || currentDST != DST);
200     }
201 
202     //--------------------- Infrastructure ---------------------------
203     static volatile int passed = 0, failed = 0;
pass()204     static void pass() {passed++;}
fail()205     static void fail() {failed++; Thread.dumpStack();}
fail(String msg)206     static void fail(String msg) {System.out.println(msg); fail();}
unexpected(Throwable t)207     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
check(boolean cond)208     static void check(boolean cond) {if (cond) pass(); else fail();}
equal(Object x, Object y)209     static void equal(Object x, Object y) {
210         if (x == null ? y == null : x.equals(y)) pass();
211         else fail(x + " not equal to " + y);}
main(String[] args)212     public static void main(String[] args) throws Throwable {
213         try {realMain(args);} catch (Throwable t) {unexpected(t);}
214         System.out.println("\nPassed = " + passed + " failed = " + failed);
215         if (failed > 0) throw new AssertionError("Some tests failed");}
216 }
217