1 /*
   2  * Copyright (c) 2015, 2019, 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.lang.ref.Reference;
  28 import java.lang.ref.ReferenceQueue;
  29 import java.lang.ref.WeakReference;
  30 import java.nio.file.Files;
  31 import java.nio.file.Paths;
  32 import java.security.CodeSource;
  33 import java.security.Permission;
  34 import java.security.PermissionCollection;
  35 import java.security.Permissions;
  36 import java.security.Policy;
  37 import java.security.ProtectionDomain;
  38 import java.util.Arrays;
  39 import java.util.Collections;
  40 import java.util.Enumeration;
  41 import java.util.List;
  42 import java.util.Properties;
  43 import java.util.UUID;
  44 import java.util.concurrent.Callable;
  45 import java.util.concurrent.atomic.AtomicBoolean;
  46 import java.util.function.BiFunction;
  47 import java.util.function.Function;
  48 import java.util.logging.FileHandler;
  49 import java.util.logging.LogManager;
  50 import java.util.logging.Logger;
  51 import java.util.logging.LoggingPermission;
  52 
  53 /**
  54  * @test
  55  * @bug 8033661
  56  * @summary tests that FileHandlers configured on abstract nodes in logging.properties
  57  *          will be closed on reset and reopened on updateConfiguration().
  58  *          Test a complex reconfiguration where a logger with handlers
  59  *          suddenly appears in the hierarchy between a child logger and the
  60  *          root logger.
  61  * @run main/othervm HandlersOnComplexResetUpdate UNSECURE
  62  * @run main/othervm HandlersOnComplexResetUpdate SECURE
  63  * @author danielfuchs
  64  */
  65 public class HandlersOnComplexResetUpdate {
  66 
  67     /**
  68      * We will test the handling of abstract logger nodes with file handlers in
  69      * two configurations:
  70      * UNSECURE: No security manager.
  71      * SECURE: With the security manager present - and the required
  72      *         permissions granted.
  73      */
  74     public static enum TestCase {
  75         UNSECURE, SECURE;
  76         public void run(List<Properties> properties) throws Exception {
  77             System.out.println("Running test case: " + name());
  78             Configure.setUp(this, properties.get(0));
  79             test(this.name(), properties);
  80         }
  81     }
  82 
  83     public static final double TIMEOUT_FACTOR;
  84     static {
  85         String toFactor = System.getProperty("test.timeout.factor", "1.0");
  86         TIMEOUT_FACTOR = Double.parseDouble(toFactor);
  87     }
  88     static int adjustCount(int count) {
  89         return (int) Math.ceil(TIMEOUT_FACTOR * count);
  90     }
  91 
  92     private static final String PREFIX =
  93             "FileHandler-" + UUID.randomUUID() + ".log";
  94     private static final String userDir = System.getProperty("user.dir", ".");
  95     private static final boolean userDirWritable = Files.isWritable(Paths.get(userDir));
  96 
  97     private static final List<Properties> properties;
  98     static {
  99         // The test will call reset() and updateConfiguration() with each of these
 100         // properties in sequence. The child logger is not released between each
 101         // configuration. What is interesting here is mostly what happens between
 102         // props4 and props5:
 103         //
 104         // In step 4 (props4) the configuration defines a handler for the
 105         // logger com.foo (the direct parent of com.foo.child - which is the
 106         // logger we hold on to).
 107         //
 108         // In step 5 (props5) the configuration has nothing concerning
 109         // 'com.foo', but the handler has been migrated to 'com'.
 110         // Since there doesn't exist any logger for 'com' (the previous
 111         // configuration didn't have any configuration for 'com'), then
 112         // 'com' will not be found when we process the existing loggers named
 113         // in the configuration.
 114         //
 115         // So if we didn't also process the existing loggers not named in the
 116         // configuration (such as com.foo.child) then no logger for 'com'
 117         // would be created, which means that com.foo.child would not be
 118         // able to inherit its configuration for 'com' until someone explicitely
 119         // creates a logger for 'com'.
 120         //
 121         // This test check that a logger for 'com' will be created because
 122         // 'com.foo.child' still exists when updateConfiguration() is called.
 123 
 124         Properties props1 = new Properties();
 125         props1.setProperty("test.name", "parent logger with handler");
 126         props1.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
 127         props1.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
 128         props1.setProperty(FileHandler.class.getName() + ".level", "ALL");
 129         props1.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
 130         props1.setProperty("com.foo.handlers", FileHandler.class.getName());
 131         props1.setProperty("test.checkHandlersOnParent", "true");
 132         props1.setProperty("test.checkHandlersOn", "com.foo");
 133         props1.setProperty("com.bar.level", "FINEST");
 134 
 135         Properties props2 = new Properties();
 136         props2.setProperty("java.util.logging.LogManager.reconfigureHandlers", "true");
 137         props2.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
 138         props2.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
 139         props2.setProperty(FileHandler.class.getName() + ".level", "ALL");
 140         props2.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
 141         props2.setProperty("com.foo.handlers", FileHandler.class.getName());
 142         props2.setProperty("test.checkHandlersOnParent", "true");
 143         props2.setProperty("test.checkHandlersOn", "com.foo");
 144         props2.setProperty("com.bar.level", "FINEST");
 145 
 146         Properties props3 = new Properties();
 147         props3.setProperty("test.name", "parent logger with handler");
 148         props3.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
 149         props3.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
 150         props3.setProperty(FileHandler.class.getName() + ".level", "ALL");
 151         props3.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
 152         props3.setProperty("com.foo.handlers", FileHandler.class.getName());
 153         props3.setProperty("test.checkHandlersOnParent", "true");
 154         props3.setProperty("test.checkHandlersOn", "com.foo");
 155         props3.setProperty("com.bar.level", "FINEST");
 156 
 157         Properties props4 = new Properties();
 158         props4.setProperty("java.util.logging.LogManager.reconfigureHandlers", "true");
 159         props4.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
 160         props4.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
 161         props4.setProperty(FileHandler.class.getName() + ".level", "ALL");
 162         props4.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
 163         props4.setProperty("test.checkHandlersOnParent", "true");
 164         props4.setProperty("test.checkHandlersOn", "com.foo");
 165         props4.setProperty("com.foo.handlers", FileHandler.class.getName());
 166 
 167         Properties props5 = new Properties();
 168         props5.setProperty("test.name", "parent logger with handler");
 169         props5.setProperty(FileHandler.class.getName() + ".pattern", PREFIX);
 170         props5.setProperty(FileHandler.class.getName() + ".limit", String.valueOf(Integer.MAX_VALUE));
 171         props5.setProperty(FileHandler.class.getName() + ".level", "ALL");
 172         props5.setProperty(FileHandler.class.getName() + ".formatter", "java.util.logging.SimpleFormatter");
 173         props5.setProperty("test.checkHandlersOnParent", "false");
 174         props5.setProperty("test.checkHandlersOn", "com");
 175         props5.setProperty("com.handlers", FileHandler.class.getName());
 176 
 177         properties = Collections.unmodifiableList(Arrays.asList(
 178                     props1, props2, props3, props4, props5));
 179     }
 180 
 181     /**
 182      * This is the main test method. The rest is infrastructure.
 183      * Creates a child of the 'com.foo' logger (com.foo.child) and holds on to
 184      * it.
 185      * <p>
 186      * Then applies all given configurations in sequence and verifies assumptions
 187      * about the handlers that com.foo should have, or not have.
 188      * In the last configuration (props5) it also verifies that the
 189      * logger 'com' has been created and has now the expected handler.
 190      * <p>
 191      * Finally releases the child logger after all configurations have been
 192      * applied.
 193      *
 194      * @param name
 195      * @param properties
 196      * @throws Exception
 197      */
 198     static void test(String name, List<Properties> properties)
 199             throws Exception {
 200 
 201         System.out.println("\nTesting: " + name);
 202         if (!userDirWritable) {
 203             throw new RuntimeException("Not writable: "+userDir);
 204         }
 205 
 206         // Then create a child of the com.foo logger.
 207         Logger fooChild = Logger.getLogger("com.foo.child");
 208         fooChild.info("hello world");
 209         Logger barChild = Logger.getLogger("com.bar.child");
 210         barChild.info("hello world");
 211 
 212         ReferenceQueue<Logger> queue = new ReferenceQueue();
 213         WeakReference<Logger> fooRef = new WeakReference<>(Logger.getLogger("com.foo"), queue);
 214         if (fooRef.get() != fooChild.getParent()) {
 215             throw new RuntimeException("Unexpected parent logger: "
 216                     + fooChild.getParent() +"\n\texpected: " + fooRef.get());
 217         }
 218         WeakReference<Logger> barRef = new WeakReference<>(Logger.getLogger("com.bar"), queue);
 219         if (barRef.get() != barChild.getParent()) {
 220             throw new RuntimeException("Unexpected parent logger: "
 221                     + barChild.getParent() +"\n\texpected: " + barRef.get());
 222         }
 223         Reference<? extends Logger> ref2;
 224         int max = adjustCount(3);
 225         barChild = null;
 226         while ((ref2 = queue.poll()) == null) {
 227             System.gc();
 228             Thread.sleep(1000);
 229             if (--max == 0) break;
 230         }
 231 
 232         Throwable failed = null;
 233         try {
 234             if (ref2 != null) {
 235                 String refName = ref2 == fooRef ? "fooRef" : ref2 == barRef ? "barRef" : "unknown";
 236                 if (ref2 != barRef) {
 237                     throw new RuntimeException("Unexpected logger reference cleared: " + refName);
 238                 } else {
 239                     System.out.println("Reference " + refName + " cleared as expected");
 240                 }
 241             } else if (ref2 == null) {
 242                 throw new RuntimeException("Expected 'barRef' to be cleared");
 243             }
 244             // Now lets try to reset, check that ref2 has no handlers, and
 245             // attempt to configure again.
 246             Properties previousProps  = properties.get(0);
 247             int expectedHandlersCount = 1;
 248             boolean checkHandlersOnParent = Boolean.parseBoolean(
 249                     previousProps.getProperty("test.checkHandlersOnParent", "true"));
 250             String checkHandlersOn = previousProps.getProperty("test.checkHandlersOn", null);
 251             for (int i=1; i<properties.size(); i++) {
 252                 System.out.println("\n*** Reconfiguration with properties["+i+"]\n");
 253                 Properties nextProps = properties.get(i);
 254                 boolean reconfigureHandlers = true;
 255 
 256                 if (checkHandlersOnParent) {
 257                     assertEquals(expectedHandlersCount,
 258                             fooChild.getParent().getHandlers().length,
 259                             "fooChild.getParent().getHandlers().length");
 260                 }
 261                 if (checkHandlersOn != null) {
 262                     Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn);
 263                     if (loggerWithHandlers == null) {
 264                         throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn);
 265                     }
 266                     assertEquals(expectedHandlersCount,
 267                             loggerWithHandlers.getHandlers().length,
 268                             checkHandlersOn + ".getHandlers().length");
 269                 }
 270 
 271                 // Reset
 272                 Configure.doPrivileged(() -> LogManager.getLogManager().reset());
 273                 assertEquals(0, fooChild.getParent().getHandlers().length, "fooChild.getParent().getHandlers().length");
 274                 if (checkHandlersOn != null) {
 275                     Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn);
 276                     if (loggerWithHandlers == null) {
 277                         throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn);
 278                     }
 279                     assertEquals(0, loggerWithHandlers.getHandlers().length,
 280                             checkHandlersOn + ".getHandlers().length");
 281                 }
 282 
 283                 if (i == 4) {
 284                     System.out.println("Last configuration...");
 285                 }
 286                 // Read configuration
 287                 Configure.doPrivileged(() -> Configure.updateConfigurationWith(nextProps, false));
 288 
 289                 expectedHandlersCount = reconfigureHandlers ? 1 : 0;
 290                 checkHandlersOnParent = Boolean.parseBoolean(
 291                     nextProps.getProperty("test.checkHandlersOnParent", "true"));
 292                 checkHandlersOn = nextProps.getProperty("test.checkHandlersOn", null);
 293 
 294                 if (checkHandlersOnParent) {
 295                     assertEquals(expectedHandlersCount,
 296                         fooChild.getParent().getHandlers().length,
 297                         "fooChild.getParent().getHandlers().length");
 298                 } else {
 299                     assertEquals(0,
 300                         fooChild.getParent().getHandlers().length,
 301                         "fooChild.getParent().getHandlers().length");
 302                 }
 303                 if (checkHandlersOn != null) {
 304                     Logger loggerWithHandlers = LogManager.getLogManager().getLogger(checkHandlersOn);
 305                     if (loggerWithHandlers == null) {
 306                         throw new RuntimeException("Logger with handlers not found: " + checkHandlersOn);
 307                     }
 308                     assertEquals(expectedHandlersCount,
 309                             loggerWithHandlers.getHandlers().length,
 310                             checkHandlersOn + ".getHandlers().length");
 311                 }
 312             }
 313         } catch (Throwable t) {
 314             failed = t;
 315         } finally {
 316             final Throwable suppressed = failed;
 317             Configure.doPrivileged(() -> LogManager.getLogManager().reset());
 318             Configure.doPrivileged(() -> {
 319                 try {
 320                     StringBuilder builder = new StringBuilder();
 321                     Files.list(Paths.get(userDir))
 322                         .filter((f) -> f.toString().contains(PREFIX))
 323                         .filter((f) -> f.toString().endsWith(".lck"))
 324                         .forEach((f) -> {
 325                                 builder.append(f.toString()).append('\n');
 326                         });
 327                     if (!builder.toString().isEmpty()) {
 328                         throw new RuntimeException("Lock files not cleaned:\n"
 329                                 + builder.toString());
 330                     }
 331                 } catch(RuntimeException | Error x) {
 332                     if (suppressed != null) x.addSuppressed(suppressed);
 333                     throw x;
 334                 } catch(Exception x) {
 335                     if (suppressed != null) x.addSuppressed(suppressed);
 336                     throw new RuntimeException(x);
 337                 }
 338             });
 339             try {
 340                 fooChild = null;
 341                 System.out.println("Setting fooChild to: " + fooChild);
 342                 while ((ref2 = queue.poll()) == null) {
 343                     System.gc();
 344                     Thread.sleep(1000);
 345                 }
 346                 if (ref2 != fooRef) {
 347                     throw new RuntimeException("Unexpected reference: "
 348                             + ref2 +"\n\texpected: " + fooRef);
 349                 }
 350                 if (ref2.get() != null) {
 351                     throw new RuntimeException("Referent not cleared: " + ref2.get());
 352                 }
 353                 System.out.println("Got fooRef after reset(), fooChild is " + fooChild);
 354              } catch(Throwable t) {
 355                 if (failed != null) t.addSuppressed(failed);
 356                 throw t;
 357              }
 358         }
 359         if (failed != null) {
 360             // should rarely happen...
 361             throw new RuntimeException(failed);
 362         }
 363 
 364     }
 365 
 366     public static void main(String... args) throws Exception {
 367 
 368 
 369         if (args == null || args.length == 0) {
 370             args = new String[] {
 371                 TestCase.UNSECURE.name(),
 372                 TestCase.SECURE.name(),
 373             };
 374         }
 375 
 376         try {
 377             for (String testName : args) {
 378                 TestCase test = TestCase.valueOf(testName);
 379                 test.run(properties);
 380             }
 381         } finally {
 382             if (userDirWritable) {
 383                 Configure.doPrivileged(() -> {
 384                     // cleanup - delete files that have been created
 385                     try {
 386                         Files.list(Paths.get(userDir))
 387                             .filter((f) -> f.toString().contains(PREFIX))
 388                             .forEach((f) -> {
 389                                 try {
 390                                     System.out.println("deleting " + f);
 391                                     Files.delete(f);
 392                                 } catch(Throwable t) {
 393                                     System.err.println("Failed to delete " + f + ": " + t);
 394                                 }
 395                             });
 396                     } catch(Throwable t) {
 397                         System.err.println("Cleanup failed to list files: " + t);
 398                         t.printStackTrace();
 399                     }
 400                 });
 401             }
 402         }
 403     }
 404 
 405     static class Configure {
 406         static Policy policy = null;
 407         static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
 408             @Override
 409             protected AtomicBoolean initialValue() {
 410                 return  new AtomicBoolean(false);
 411             }
 412         };
 413         static void setUp(TestCase test, Properties propertyFile) {
 414             switch (test) {
 415                 case SECURE:
 416                     if (policy == null && System.getSecurityManager() != null) {
 417                         throw new IllegalStateException("SecurityManager already set");
 418                     } else if (policy == null) {
 419                         policy = new SimplePolicy(TestCase.SECURE, allowAll);
 420                         Policy.setPolicy(policy);
 421                         System.setSecurityManager(new SecurityManager());
 422                     }
 423                     if (System.getSecurityManager() == null) {
 424                         throw new IllegalStateException("No SecurityManager.");
 425                     }
 426                     if (policy == null) {
 427                         throw new IllegalStateException("policy not configured");
 428                     }
 429                     break;
 430                 case UNSECURE:
 431                     if (System.getSecurityManager() != null) {
 432                         throw new IllegalStateException("SecurityManager already set");
 433                     }
 434                     break;
 435                 default:
 436                     new InternalError("No such testcase: " + test);
 437             }
 438             doPrivileged(() -> {
 439                 updateConfigurationWith(propertyFile, false);
 440             });
 441         }
 442 
 443         static void updateConfigurationWith(Properties propertyFile, boolean append) {
 444             try {
 445                 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
 446                 propertyFile.store(bytes, propertyFile.getProperty("test.name"));
 447                 ByteArrayInputStream bais = new ByteArrayInputStream(bytes.toByteArray());
 448                 Function<String, BiFunction<String,String,String>> remapper =
 449                         append ? (x) -> ((o, n) -> n == null ? o : n)
 450                                : (x) -> ((o, n) -> n);
 451                 LogManager.getLogManager().updateConfiguration(bais, remapper);
 452             } catch (IOException ex) {
 453                 throw new RuntimeException(ex);
 454             }
 455         }
 456 
 457         static void doPrivileged(Runnable run) {
 458             final boolean old = allowAll.get().getAndSet(true);
 459             try {
 460                 run.run();
 461             } finally {
 462                 allowAll.get().set(old);
 463             }
 464         }
 465         static <T> T callPrivileged(Callable<T> call) throws Exception {
 466             final boolean old = allowAll.get().getAndSet(true);
 467             try {
 468                 return call.call();
 469             } finally {
 470                 allowAll.get().set(old);
 471             }
 472         }
 473     }
 474 
 475     @FunctionalInterface
 476     public static interface FileHandlerSupplier {
 477         public FileHandler test() throws Exception;
 478     }
 479 
 480     static final class TestAssertException extends RuntimeException {
 481         TestAssertException(String msg) {
 482             super(msg);
 483         }
 484     }
 485 
 486     private static void assertEquals(long expected, long received, String msg) {
 487         if (expected != received) {
 488             throw new TestAssertException("Unexpected result for " + msg
 489                     + ".\n\texpected: " + expected
 490                     +  "\n\tactual:   " + received);
 491         } else {
 492             System.out.println("Got expected " + msg + ": " + received);
 493         }
 494     }
 495 
 496     final static class PermissionsBuilder {
 497         final Permissions perms;
 498         public PermissionsBuilder() {
 499             this(new Permissions());
 500         }
 501         public PermissionsBuilder(Permissions perms) {
 502             this.perms = perms;
 503         }
 504         public PermissionsBuilder add(Permission p) {
 505             perms.add(p);
 506             return this;
 507         }
 508         public PermissionsBuilder addAll(PermissionCollection col) {
 509             if (col != null) {
 510                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
 511                     perms.add(e.nextElement());
 512                 }
 513             }
 514             return this;
 515         }
 516         public Permissions toPermissions() {
 517             final PermissionsBuilder builder = new PermissionsBuilder();
 518             builder.addAll(perms);
 519             return builder.perms;
 520         }
 521     }
 522 
 523     public static class SimplePolicy extends Policy {
 524 
 525         final Permissions permissions;
 526         final Permissions allPermissions;
 527         final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale
 528         public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
 529             this.allowAll = allowAll;
 530             permissions = new Permissions();
 531             permissions.add(new LoggingPermission("control", null));
 532             permissions.add(new FilePermission(PREFIX+".lck", "read,write,delete"));
 533             permissions.add(new FilePermission(PREFIX, "read,write"));
 534 
 535             // these are used for configuring the test itself...
 536             allPermissions = new Permissions();
 537             allPermissions.add(new java.security.AllPermission());
 538 
 539         }
 540 
 541         @Override
 542         public boolean implies(ProtectionDomain domain, Permission permission) {
 543             if (allowAll.get().get()) return allPermissions.implies(permission);
 544             return permissions.implies(permission);
 545         }
 546 
 547         @Override
 548         public PermissionCollection getPermissions(CodeSource codesource) {
 549             return new PermissionsBuilder().addAll(allowAll.get().get()
 550                     ? allPermissions : permissions).toPermissions();
 551         }
 552 
 553         @Override
 554         public PermissionCollection getPermissions(ProtectionDomain domain) {
 555             return new PermissionsBuilder().addAll(allowAll.get().get()
 556                     ? allPermissions : permissions).toPermissions();
 557         }
 558     }
 559 
 560 }