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