1 /* 2 * Copyright (c) 2014, 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 String msg = "this is at least 10 chars long"; 350 fh.publish(new LogRecord(Level.SEVERE, msg)); 351 fh.flush(); 352 long w = getWritten(metered); 353 long offset = getWritten(metered); 354 System.out.println("first offset is: " + offset); 355 356 fh.publish(new LogRecord(Level.SEVERE, msg)); 357 fh.flush(); 358 offset = getWritten(metered) - w; 359 w = getWritten(metered); 360 System.out.println("second offset is: " + offset); 361 362 fh.publish(new LogRecord(Level.SEVERE, msg)); 363 fh.flush(); 364 offset = getWritten(metered) - w; 365 w = getWritten(metered); 366 System.out.println("third offset is: " + offset); 367 368 fh.publish(new LogRecord(Level.SEVERE, msg)); 369 fh.flush(); 370 offset = getWritten(metered) - w; 371 System.out.println("fourth offset is: " + offset); 372 373 // Now set fh.meter.written to something close to the limit, 374 // so that we can trigger log file rotation. 375 assertEquals(limit-2*offset+10, setWritten(metered, limit-2*offset+10), "written"); 376 w = getWritten(metered); 377 378 // publish one more log record. we should still be just beneath 379 // the limit 380 fh.publish(new LogRecord(Level.SEVERE, msg)); 381 fh.flush(); 382 assertEquals(w+offset, getWritten(metered), "written"); 383 384 // check that fh still has the same MeteredStream - indicating 385 // that the file hasn't rotated. 386 if (getMeteredOutput(fh) != metered) { 387 throw new RuntimeException("Log should not have rotated"); 388 } 389 390 // Now publish two log record. The spec is a bit vague about when 391 // exactly the log will be rotated - it could happen just after 392 // writing the first log record or just before writing the next 393 // one. We publich two - so we're sure that the log must have 394 // rotated. 395 fh.publish(new LogRecord(Level.SEVERE, msg)); 396 fh.flush(); 397 fh.publish(new LogRecord(Level.SEVERE, msg)); 398 fh.flush(); 399 400 // Check that fh.meter is a different instance of MeteredStream. 401 if (getMeteredOutput(fh) == metered) { 402 throw new RuntimeException("Log should have rotated"); 403 } 404 // success! 405 return fh; 406 } catch (Error | Exception x) { 407 // if we get an exception we need to close fh. 408 // if we don't get an exception, fh will be closed by the caller. 409 // (and that's why we dont use try-with-resources/finally here). 410 try { fh.close(); } catch(Throwable t) {t.printStackTrace();} 411 throw x; 412 } 413 } 414 415 public static void test(String name, Properties props, long limit) throws Exception { 416 System.out.println("Testing: " + name); 417 Class<? extends Exception> expectedException = null; 418 419 if (userDirWritable || expectedException != null) { 420 // These calls will create files in user.dir. 421 // The file name contain a random UUID (PREFIX) which identifies them 422 // and allow us to remove them cleanly at the end (see finally block 423 // in main()). 424 checkException(expectedException, () -> new FileHandler()); 425 checkException(expectedException, () -> { 426 final FileHandler fh = new FileHandler(); 427 assertEquals(limit, getLimit(fh), "limit"); 428 return fh; 429 }); 430 checkException(expectedException, () -> testFileHandlerLimit( 431 () -> new FileHandler(), 432 limit)); 433 checkException(expectedException, () -> testFileHandlerLimit( 434 () -> new FileHandler(PREFIX, Long.MAX_VALUE, 1, true), 435 Long.MAX_VALUE)); 436 } 437 } 438 439 440 final static class PermissionsBuilder { 441 final Permissions perms; 442 public PermissionsBuilder() { 443 this(new Permissions()); 444 } 445 public PermissionsBuilder(Permissions perms) { 446 this.perms = perms; 447 } 448 public PermissionsBuilder add(Permission p) { 449 perms.add(p); 450 return this; 451 } 452 public PermissionsBuilder addAll(PermissionCollection col) { 453 if (col != null) { 454 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) { 455 perms.add(e.nextElement()); 456 } 457 } 458 return this; 459 } 460 public Permissions toPermissions() { 461 final PermissionsBuilder builder = new PermissionsBuilder(); 462 builder.addAll(perms); 463 return builder.perms; 464 } 465 } 466 467 public static class SimplePolicy extends Policy { 468 469 final Permissions permissions; 470 final Permissions allPermissions; 471 final AtomicBoolean allowAll; 472 public SimplePolicy(TestCase test, AtomicBoolean allowAll) { 473 this.allowAll = allowAll; 474 permissions = new Permissions(); 475 permissions.add(new LoggingPermission("control", null)); 476 permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete")); 477 permissions.add(new FilePermission(PREFIX, "read,write")); 478 479 // these are used for configuring the test itself... 480 allPermissions = new Permissions(); 481 allPermissions.add(new java.security.AllPermission()); 482 483 } 484 485 @Override 486 public boolean implies(ProtectionDomain domain, Permission permission) { 487 if (allowAll.get()) return allPermissions.implies(permission); 488 return permissions.implies(permission); 489 } 490 491 @Override 492 public PermissionCollection getPermissions(CodeSource codesource) { 493 return new PermissionsBuilder().addAll(allowAll.get() 494 ? allPermissions : permissions).toPermissions(); 495 } 496 497 @Override 498 public PermissionCollection getPermissions(ProtectionDomain domain) { 499 return new PermissionsBuilder().addAll(allowAll.get() 500 ? allPermissions : permissions).toPermissions(); 501 } 502 } 503 504 }