1 /* 2 * Copyright (c) 2014, 2015, 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 import java.io.ByteArrayInputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.io.FilePermission; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.lang.reflect.Field; 29 import java.nio.file.Files; 30 import java.nio.file.Paths; 31 import java.security.CodeSource; 32 import java.security.Permission; 33 import java.security.PermissionCollection; 34 import java.security.Permissions; 35 import java.security.Policy; 36 import java.security.ProtectionDomain; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.Enumeration; 40 import java.util.List; 41 import java.util.Properties; 42 import java.util.UUID; 43 import java.util.concurrent.Callable; 44 import java.util.concurrent.atomic.AtomicBoolean; 45 import java.util.logging.FileHandler; 46 import java.util.logging.Level; 47 import java.util.logging.LogManager; 48 import java.util.logging.LogRecord; 49 import java.util.logging.LoggingPermission; 50 51 /** 52 * @test 53 * @bug 8059767 54 * @summary tests that FileHandler can accept a long limit. 55 * @modules java.logging/java.util.logging:open 56 * @run main/othervm FileHandlerLongLimit UNSECURE 57 * @run main/othervm FileHandlerLongLimit SECURE 58 * @author danielfuchs 59 * @key randomness 60 */ 61 public class FileHandlerLongLimit { 62 63 /** 64 * We will test handling of limit and overflow of MeteredStream.written in 65 * two configurations. 66 * UNSECURE: No security manager. 67 * SECURE: With the security manager present - and the required 68 * permissions granted. 69 */ 70 public static enum TestCase { 71 UNSECURE, SECURE; run(Properties propertyFile)72 public void run(Properties propertyFile) throws Exception { 73 System.out.println("Running test case: " + name()); 74 Configure.setUp(this, propertyFile); 75 test(this.name() + " " + propertyFile.getProperty("test.name"), propertyFile, 76 Long.parseLong(propertyFile.getProperty(FileHandler.class.getName()+".limit"))); 77 } 78 } 79 80 81 private static final String PREFIX = 82 "FileHandler-" + UUID.randomUUID() + ".log"; 83 private static final String userDir = System.getProperty("user.dir", "."); 84 private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir)); 85 private static final Field limitField; 86 private static final Field meterField; 87 private static final Field writtenField; 88 private static final Field outField; 89 90 private static final List<Properties> properties; 91 static { 92 Properties props1 = new Properties(); 93 Properties props2 = new Properties(); 94 Properties props3 = new Properties(); 95 props1.setProperty("test.name", "with limit=Integer.MAX_VALUE"); FileHandler.class.getName()96 props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); String.valueOf(Integer.MAX_VALUE)97 props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE)); 98 props2.setProperty("test.name", "with limit=Integer.MAX_VALUE*4"); FileHandler.class.getName()99 props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); String.valueOf((long)Integer.MAX_VALUE)*4100 props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(((long)Integer.MAX_VALUE)*4)); 101 props3.setProperty("test.name", "with limit=Long.MAX_VALUE - 1024"); FileHandler.class.getName()102 props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX); FileHandler.class.getName()103 props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Long.MAX_VALUE - 1024)); 104 properties = Collections.unmodifiableList(Arrays.asList( 105 props1, 106 props2, 107 props3)); 108 try { 109 Class<?> metteredStreamClass = Class.forName(FileHandler.class.getName()+"$MeteredStream"); 110 limitField = FileHandler.class.getDeclaredField("limit"); 111 limitField.setAccessible(true); 112 meterField = FileHandler.class.getDeclaredField("meter"); 113 meterField.setAccessible(true); 114 writtenField = metteredStreamClass.getDeclaredField("written"); 115 writtenField.setAccessible(true); 116 outField = metteredStreamClass.getDeclaredField("out"); 117 outField.setAccessible(true); 118 119 } catch (NoSuchFieldException | ClassNotFoundException x) { 120 throw new ExceptionInInitializerError(x); 121 } 122 } 123 124 private static class TestOutputStream extends OutputStream { 125 final OutputStream delegate; TestOutputStream(OutputStream delegate)126 TestOutputStream(OutputStream delegate) { 127 this.delegate = delegate; 128 } 129 @Override write(int b)130 public void write(int b) throws IOException { 131 // do nothing - we only pretend to write something... 132 } 133 @Override close()134 public void close() throws IOException { 135 delegate.close(); 136 } 137 138 @Override flush()139 public void flush() throws IOException { 140 delegate.flush(); 141 } 142 143 } 144 main(String... args)145 public static void main(String... args) throws Exception { 146 147 148 if (args == null || args.length == 0) { 149 args = new String[] { 150 TestCase.UNSECURE.name(), 151 TestCase.SECURE.name(), 152 }; 153 } 154 155 try { 156 for (String testName : args) { 157 for (Properties propertyFile : properties) { 158 TestCase test = TestCase.valueOf(testName); 159 test.run(propertyFile); 160 } 161 } 162 } finally { 163 if (userDirWritable) { 164 Configure.doPrivileged(() -> { 165 // cleanup - delete files that have been created 166 try { 167 Files.list(Paths.get(userDir)) 168 .filter((f) -> f.toString().contains(PREFIX)) 169 .forEach((f) -> { 170 try { 171 System.out.println("deleting " + f); 172 Files.delete(f); 173 } catch(Throwable t) { 174 System.err.println("Failed to delete " + f + ": " + t); 175 } 176 }); 177 } catch(Throwable t) { 178 System.err.println("Cleanup failed to list files: " + t); 179 t.printStackTrace(); 180 } 181 }); 182 } 183 } 184 } 185 186 static class Configure { 187 static Policy policy = null; 188 static final AtomicBoolean allowAll = new AtomicBoolean(false); setUp(TestCase test, Properties propertyFile)189 static void setUp(TestCase test, Properties propertyFile) { 190 switch (test) { 191 case SECURE: 192 if (policy == null && System.getSecurityManager() != null) { 193 throw new IllegalStateException("SecurityManager already set"); 194 } else if (policy == null) { 195 policy = new SimplePolicy(TestCase.SECURE, allowAll); 196 Policy.setPolicy(policy); 197 System.setSecurityManager(new SecurityManager()); 198 } 199 if (System.getSecurityManager() == null) { 200 throw new IllegalStateException("No SecurityManager."); 201 } 202 if (policy == null) { 203 throw new IllegalStateException("policy not configured"); 204 } 205 break; 206 case UNSECURE: 207 if (System.getSecurityManager() != null) { 208 throw new IllegalStateException("SecurityManager already set"); 209 } 210 break; 211 default: 212 new InternalError("No such testcase: " + test); 213 } 214 doPrivileged(() -> { 215 try { 216 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 217 propertyFile.store(bytes, propertyFile.getProperty("test.name")); 218 ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray()); 219 LogManager.getLogManager().readConfiguration(bais); 220 } catch (IOException ex) { 221 throw new RuntimeException(ex); 222 } 223 }); 224 } doPrivileged(Runnable run)225 static void doPrivileged(Runnable run) { 226 allowAll.set(true); 227 try { 228 run.run(); 229 } finally { 230 allowAll.set(false); 231 } 232 } callPrivileged(Callable<T> call)233 static <T> T callPrivileged(Callable<T> call) throws Exception { 234 allowAll.set(true); 235 try { 236 return call.call(); 237 } finally { 238 allowAll.set(false); 239 } 240 } 241 } 242 243 @FunctionalInterface 244 public static interface FileHandlerSupplier { test()245 public FileHandler test() throws Exception; 246 } 247 checkException(Class<? extends Exception> type, FileHandlerSupplier test)248 private static void checkException(Class<? extends Exception> type, FileHandlerSupplier test) { 249 Throwable t = null; 250 FileHandler f = null; 251 try { 252 f = test.test(); 253 } catch (Throwable x) { 254 t = x; 255 } 256 try { 257 if (type != null && t == null) { 258 throw new RuntimeException("Expected " + type.getName() + " not thrown"); 259 } else if (type != null && t != null) { 260 if (type.isInstance(t)) { 261 System.out.println("Recieved expected exception: " + t); 262 } else { 263 throw new RuntimeException("Exception type mismatch: " 264 + type.getName() + " expected, " 265 + t.getClass().getName() + " received.", t); 266 } 267 } else if (t != null) { 268 throw new RuntimeException("Unexpected exception received: " + t, t); 269 } 270 } finally { 271 if (f != null) { 272 // f should always be null when an exception is expected, 273 // but in case the test doesn't behave as expected we will 274 // want to close f. 275 try { f.close(); } catch (Throwable x) {}; 276 } 277 } 278 } 279 280 static final class TestAssertException extends RuntimeException { TestAssertException(String msg)281 TestAssertException(String msg) { 282 super(msg); 283 } 284 } 285 assertEquals(long expected, long received, String msg)286 private static void assertEquals(long expected, long received, String msg) { 287 if (expected != received) { 288 throw new TestAssertException("Unexpected result for " + msg 289 + ".\n\texpected: " + expected 290 + "\n\tactual: " + received); 291 } else { 292 System.out.println("Got expected " + msg + ": " + received); 293 } 294 } 295 getLimit(FileHandler handler)296 private static long getLimit(FileHandler handler) throws Exception { 297 return Configure.callPrivileged((Callable<Long>)() -> { 298 return limitField.getLong(handler); 299 }); 300 } getMeteredOutput(FileHandler handler)301 private static OutputStream getMeteredOutput(FileHandler handler) throws Exception { 302 return Configure.callPrivileged((Callable<OutputStream>)() -> { 303 final OutputStream metered = OutputStream.class.cast(meterField.get(handler)); 304 return metered; 305 }); 306 } 307 private static TestOutputStream setTestOutputStream(OutputStream metered) throws Exception { 308 return Configure.callPrivileged((Callable<TestOutputStream>)() -> { 309 outField.set(metered, new TestOutputStream(OutputStream.class.cast(outField.get(metered)))); 310 return TestOutputStream.class.cast(outField.get(metered)); 311 }); 312 } 313 private static long getWritten(OutputStream metered) throws Exception { 314 return Configure.callPrivileged((Callable<Long>)() -> { 315 return writtenField.getLong(metered); 316 }); 317 } 318 319 private static long setWritten(OutputStream metered, long newValue) throws Exception { 320 return Configure.callPrivileged((Callable<Long>)() -> { 321 writtenField.setLong(metered, newValue); 322 return writtenField.getLong(metered); 323 }); 324 } 325 326 public static FileHandler testFileHandlerLimit(FileHandlerSupplier supplier, 327 long limit) throws Exception { 328 Configure.doPrivileged(() -> { 329 try { 330 Files.deleteIfExists(Paths.get(PREFIX)); 331 } catch (IOException x) { 332 throw new RuntimeException(x); 333 } 334 }); 335 final FileHandler fh = supplier.test(); 336 try { 337 // verify we have the expected limit 338 assertEquals(limit, getLimit(fh), "limit"); 339 340 // get the metered output stream 341 OutputStream metered = getMeteredOutput(fh); 342 343 // we don't want to actually write to the file, so let's 344 // redirect the metered to our own TestOutputStream. 345 setTestOutputStream(metered); 346 347 // check that fh.meter.written is 0 348 assertEquals(0, getWritten(metered), "written"); 349 350 // now we're going to publish a series of log records 351 // we're using the same log record over and over to make 352 // sure we get the same amount of bytes. 353 String msg = "this is at least 10 chars long"; 354 LogRecord record = new LogRecord(Level.SEVERE, msg); 355 fh.publish(record); 356 fh.flush(); 357 long w = getWritten(metered); 358 long offset = getWritten(metered); 359 System.out.println("first offset is: " + offset); 360 361 fh.publish(record); 362 fh.flush(); 363 offset = getWritten(metered) - w; 364 w = getWritten(metered); 365 System.out.println("second offset is: " + offset); 366 367 fh.publish(record); 368 fh.flush(); 369 offset = getWritten(metered) - w; 370 w = getWritten(metered); 371 System.out.println("third offset is: " + offset); 372 373 fh.publish(record); 374 fh.flush(); 375 offset = getWritten(metered) - w; 376 System.out.println("fourth offset is: " + offset); 377 378 // Now set fh.meter.written to something close to the limit, 379 // so that we can trigger log file rotation. 380 assertEquals(limit-2*offset+10, setWritten(metered, limit-2*offset+10), "written"); 381 w = getWritten(metered); 382 383 // publish one more log record. we should still be just beneath 384 // the limit 385 fh.publish(record); 386 fh.flush(); 387 assertEquals(w+offset, getWritten(metered), "written"); 388 389 // check that fh still has the same MeteredStream - indicating 390 // that the file hasn't rotated. 391 if (getMeteredOutput(fh) != metered) { 392 throw new RuntimeException("Log should not have rotated"); 393 } 394 395 // Now publish two log record. The spec is a bit vague about when 396 // exactly the log will be rotated - it could happen just after 397 // writing the first log record or just before writing the next 398 // one. We publich two - so we're sure that the log must have 399 // rotated. 400 fh.publish(record); 401 fh.flush(); 402 fh.publish(record); 403 fh.flush(); 404 405 // Check that fh.meter is a different instance of MeteredStream. 406 if (getMeteredOutput(fh) == metered) { 407 throw new RuntimeException("Log should have rotated"); 408 } 409 // success! 410 return fh; 411 } catch (Error | Exception x) { 412 // if we get an exception we need to close fh. 413 // if we don't get an exception, fh will be closed by the caller. 414 // (and that's why we dont use try-with-resources/finally here). 415 try { fh.close(); } catch(Throwable t) {t.printStackTrace();} 416 throw x; 417 } 418 } 419 420 public static void test(String name, Properties props, long limit) throws Exception { 421 System.out.println("Testing: " + name); 422 Class<? extends Exception> expectedException = null; 423 424 if (userDirWritable || expectedException != null) { 425 // These calls will create files in user.dir. 426 // The file name contain a random UUID (PREFIX) which identifies them 427 // and allow us to remove them cleanly at the end (see finally block 428 // in main()). 429 checkException(expectedException, () -> new FileHandler()); 430 checkException(expectedException, () -> { 431 final FileHandler fh = new FileHandler(); 432 assertEquals(limit, getLimit(fh), "limit"); 433 return fh; 434 }); 435 checkException(expectedException, () -> testFileHandlerLimit( 436 () -> new FileHandler(), 437 limit)); 438 checkException(expectedException, () -> testFileHandlerLimit( 439 () -> new FileHandler(PREFIX, Long.MAX_VALUE, 1, true), 440 Long.MAX_VALUE)); 441 } 442 } 443 444 445 static final class PermissionsBuilder { 446 final Permissions perms; 447 public PermissionsBuilder() { 448 this(new Permissions()); 449 } 450 public PermissionsBuilder(Permissions perms) { 451 this.perms = perms; 452 } 453 public PermissionsBuilder add(Permission p) { 454 perms.add(p); 455 return this; 456 } 457 public PermissionsBuilder addAll(PermissionCollection col) { 458 if (col != null) { 459 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) { 460 perms.add(e.nextElement()); 461 } 462 } 463 return this; 464 } 465 public Permissions toPermissions() { 466 final PermissionsBuilder builder = new PermissionsBuilder(); 467 builder.addAll(perms); 468 return builder.perms; 469 } 470 } 471 472 public static class SimplePolicy extends Policy { 473 474 final Permissions permissions; 475 final Permissions allPermissions; 476 final AtomicBoolean allowAll; 477 public SimplePolicy(TestCase test, AtomicBoolean allowAll) { 478 this.allowAll = allowAll; 479 permissions = new Permissions(); 480 permissions.add(new LoggingPermission("control", null)); 481 permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete")); 482 permissions.add(new FilePermission(PREFIX, "read,write")); 483 484 // these are used for configuring the test itself... 485 allPermissions = new Permissions(); 486 allPermissions.add(new java.security.AllPermission()); 487 488 } 489 490 @Override 491 public boolean implies(ProtectionDomain domain, Permission permission) { 492 if (allowAll.get()) return allPermissions.implies(permission); 493 return permissions.implies(permission); 494 } 495 496 @Override 497 public PermissionCollection getPermissions(CodeSource codesource) { 498 return new PermissionsBuilder().addAll(allowAll.get() 499 ? allPermissions : permissions).toPermissions(); 500 } 501 502 @Override 503 public PermissionCollection getPermissions(ProtectionDomain domain) { 504 return new PermissionsBuilder().addAll(allowAll.get() 505 ? allPermissions : permissions).toPermissions(); 506 } 507 } 508 509 } 510