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