1 /*
   2  * Copyright (c) 2000, 2017, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.logging;
  27 
  28 import static java.nio.file.StandardOpenOption.APPEND;
  29 import static java.nio.file.StandardOpenOption.CREATE_NEW;
  30 import static java.nio.file.StandardOpenOption.WRITE;
  31 
  32 import java.io.BufferedOutputStream;
  33 import java.io.File;
  34 import java.io.FileOutputStream;
  35 import java.io.IOException;
  36 import java.io.OutputStream;
  37 import java.nio.channels.FileChannel;
  38 import java.nio.channels.OverlappingFileLockException;
  39 import java.nio.file.FileAlreadyExistsException;
  40 import java.nio.file.Files;
  41 import java.nio.file.LinkOption;
  42 import java.nio.file.NoSuchFileException;
  43 import java.nio.file.Path;
  44 import java.nio.file.Paths;
  45 import java.security.AccessController;
  46 import java.security.PrivilegedAction;
  47 import java.util.HashSet;
  48 import java.util.Set;
  49 
  50 /**
  51  * Simple file logging <tt>Handler</tt>.
  52  * <p>
  53  * The <tt>FileHandler</tt> can either write to a specified file,
  54  * or it can write to a rotating set of files.
  55  * <p>
  56  * For a rotating set of files, as each file reaches a given size
  57  * limit, it is closed, rotated out, and a new file opened.
  58  * Successively older files are named by adding "0", "1", "2",
  59  * etc. into the base filename.
  60  * <p>
  61  * By default buffering is enabled in the IO libraries but each log
  62  * record is flushed out when it is complete.
  63  * <p>
  64  * By default the <tt>XMLFormatter</tt> class is used for formatting.
  65  * <p>
  66  * <b>Configuration:</b>
  67  * By default each <tt>FileHandler</tt> is initialized using the following
  68  * <tt>LogManager</tt> configuration properties where <tt>&lt;handler-name&gt;</tt>
  69  * refers to the fully-qualified class name of the handler.
  70  * If properties are not defined
  71  * (or have invalid values) then the specified default values are used.
  72  * <ul>
  73  * <li>   &lt;handler-name&gt;.level
  74  *        specifies the default level for the <tt>Handler</tt>
  75  *        (defaults to <tt>Level.ALL</tt>). </li>
  76  * <li>   &lt;handler-name&gt;.filter
  77  *        specifies the name of a <tt>Filter</tt> class to use
  78  *        (defaults to no <tt>Filter</tt>). </li>
  79  * <li>   &lt;handler-name&gt;.formatter
  80  *        specifies the name of a <tt>Formatter</tt> class to use
  81  *        (defaults to <tt>java.util.logging.XMLFormatter</tt>) </li>
  82  * <li>   &lt;handler-name&gt;.encoding
  83  *        the name of the character set encoding to use (defaults to
  84  *        the default platform encoding). </li>
  85  * <li>   &lt;handler-name&gt;.limit
  86  *        specifies an approximate maximum amount to write (in bytes)
  87  *        to any one file.  If this is zero, then there is no limit.
  88  *        (Defaults to no limit). </li>
  89  * <li>   &lt;handler-name&gt;.count
  90  *        specifies how many output files to cycle through (defaults to 1). </li>
  91  * <li>   &lt;handler-name&gt;.pattern
  92  *        specifies a pattern for generating the output file name.  See
  93  *        below for details. (Defaults to "%h/java%u.log"). </li>
  94  * <li>   &lt;handler-name&gt;.append
  95  *        specifies whether the FileHandler should append onto
  96  *        any existing files (defaults to false). </li>
  97  * </ul>
  98  * <p>
  99  * For example, the properties for {@code FileHandler} would be:
 100  * <ul>
 101  * <li>   java.util.logging.FileHandler.level=INFO </li>
 102  * <li>   java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter </li>
 103  * </ul>
 104  * <p>
 105  * For a custom handler, e.g. com.foo.MyHandler, the properties would be:
 106  * <ul>
 107  * <li>   com.foo.MyHandler.level=INFO </li>
 108  * <li>   com.foo.MyHandler.formatter=java.util.logging.SimpleFormatter </li>
 109  * </ul>
 110  * <p>
 111  * A pattern consists of a string that includes the following special
 112  * components that will be replaced at runtime:
 113  * <ul>
 114  * <li>    "/"    the local pathname separator </li>
 115  * <li>     "%t"   the system temporary directory </li>
 116  * <li>     "%h"   the value of the "user.home" system property </li>
 117  * <li>     "%g"   the generation number to distinguish rotated logs </li>
 118  * <li>     "%u"   a unique number to resolve conflicts </li>
 119  * <li>     "%%"   translates to a single percent sign "%" </li>
 120  * </ul>
 121  * If no "%g" field has been specified and the file count is greater
 122  * than one, then the generation number will be added to the end of
 123  * the generated filename, after a dot.
 124  * <p>
 125  * Thus for example a pattern of "%t/java%g.log" with a count of 2
 126  * would typically cause log files to be written on Solaris to
 127  * /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they
 128  * would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log
 129  * <p>
 130  * Generation numbers follow the sequence 0, 1, 2, etc.
 131  * <p>
 132  * Normally the "%u" unique field is set to 0.  However, if the <tt>FileHandler</tt>
 133  * tries to open the filename and finds the file is currently in use by
 134  * another process it will increment the unique number field and try
 135  * again.  This will be repeated until <tt>FileHandler</tt> finds a file name that
 136  * is  not currently in use. If there is a conflict and no "%u" field has
 137  * been specified, it will be added at the end of the filename after a dot.
 138  * (This will be after any automatically added generation number.)
 139  * <p>
 140  * Thus if three processes were all trying to log to fred%u.%g.txt then
 141  * they  might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as
 142  * the first file in their rotating sequences.
 143  * <p>
 144  * Note that the use of unique ids to avoid conflicts is only guaranteed
 145  * to work reliably when using a local disk file system.
 146  *
 147  * @since 1.4
 148  */
 149 
 150 public class FileHandler extends StreamHandler {
 151     private MeteredStream meter;
 152     private boolean append;
 153     private int limit;       // zero => no limit.
 154     private int count;
 155     private String pattern;
 156     private String lockFileName;
 157     private FileChannel lockFileChannel;
 158     private File files[];
 159     private static final int DEFAULT_MAX_LOCKS = 100;
 160     private static int maxLocks;
 161     private static final Set<String> locks = new HashSet<>();
 162     
 163     /*
 164      * Initialize maxLocks from the System property if set.
 165      * If invalid/no property is provided 100 will be used as a default value.
 166      */
 167     static {
 168         maxLocks = java.security.AccessController.doPrivileged(
 169                 (PrivilegedAction<Integer>) () ->
 170                         Integer.getInteger(
 171                                 "jdk.internal.FileHandlerLogging.maxLocks",
 172                                 DEFAULT_MAX_LOCKS)
 173         );
 174 
 175         if (maxLocks <= 0) {
 176             maxLocks = DEFAULT_MAX_LOCKS;
 177         }
 178     }
 179 
 180     /**
 181      * A metered stream is a subclass of OutputStream that
 182      * (a) forwards all its output to a target stream
 183      * (b) keeps track of how many bytes have been written
 184      */
 185     private class MeteredStream extends OutputStream {
 186         final OutputStream out;
 187         int written;
 188 
 189         MeteredStream(OutputStream out, int written) {
 190             this.out = out;
 191             this.written = written;
 192         }
 193 
 194         @Override
 195         public void write(int b) throws IOException {
 196             out.write(b);
 197             written++;
 198         }
 199 
 200         @Override
 201         public void write(byte buff[]) throws IOException {
 202             out.write(buff);
 203             written += buff.length;
 204         }
 205 
 206         @Override
 207         public void write(byte buff[], int off, int len) throws IOException {
 208             out.write(buff,off,len);
 209             written += len;
 210         }
 211 
 212         @Override
 213         public void flush() throws IOException {
 214             out.flush();
 215         }
 216 
 217         @Override
 218         public void close() throws IOException {
 219             out.close();
 220         }
 221     }
 222 
 223     private void open(File fname, boolean append) throws IOException {
 224         int len = 0;
 225         if (append) {
 226             len = (int)fname.length();
 227         }
 228         FileOutputStream fout = new FileOutputStream(fname.toString(), append);
 229         BufferedOutputStream bout = new BufferedOutputStream(fout);
 230         meter = new MeteredStream(bout, len);
 231         setOutputStream(meter);
 232     }
 233 
 234     /**
 235      * Configure a FileHandler from LogManager properties and/or default values
 236      * as specified in the class javadoc.
 237      */
 238     private void configure() {
 239         LogManager manager = LogManager.getLogManager();
 240 
 241         String cname = getClass().getName();
 242 
 243         pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log");
 244         limit = manager.getIntProperty(cname + ".limit", 0);
 245         if (limit < 0) {
 246             limit = 0;
 247         }
 248         count = manager.getIntProperty(cname + ".count", 1);
 249         if (count <= 0) {
 250             count = 1;
 251         }
 252         append = manager.getBooleanProperty(cname + ".append", false);
 253         setLevel(manager.getLevelProperty(cname + ".level", Level.ALL));
 254         setFilter(manager.getFilterProperty(cname + ".filter", null));
 255         setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter()));
 256         try {
 257             setEncoding(manager.getStringProperty(cname +".encoding", null));
 258         } catch (Exception ex) {
 259             try {
 260                 setEncoding(null);
 261             } catch (Exception ex2) {
 262                 // doing a setEncoding with null should always work.
 263                 // assert false;
 264             }
 265         }
 266     }
 267 
 268 
 269     /**
 270      * Construct a default <tt>FileHandler</tt>.  This will be configured
 271      * entirely from <tt>LogManager</tt> properties (or their default values).
 272      * <p>
 273      * @exception  IOException if there are IO problems opening the files.
 274      * @exception  SecurityException  if a security manager exists and if
 275      *             the caller does not have <tt>LoggingPermission("control"))</tt>.
 276      * @exception  NullPointerException if pattern property is an empty String.
 277      */
 278     public FileHandler() throws IOException, SecurityException {
 279         checkPermission();
 280         configure();
 281         openFiles();
 282     }
 283 
 284     /**
 285      * Initialize a <tt>FileHandler</tt> to write to the given filename.
 286      * <p>
 287      * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
 288      * properties (or their default values) except that the given pattern
 289      * argument is used as the filename pattern, the file limit is
 290      * set to no limit, and the file count is set to one.
 291      * <p>
 292      * There is no limit on the amount of data that may be written,
 293      * so use this with care.
 294      *
 295      * @param pattern  the name of the output file
 296      * @exception  IOException if there are IO problems opening the files.
 297      * @exception  SecurityException  if a security manager exists and if
 298      *             the caller does not have <tt>LoggingPermission("control")</tt>.
 299      * @exception  IllegalArgumentException if pattern is an empty string
 300      */
 301     public FileHandler(String pattern) throws IOException, SecurityException {
 302         if (pattern.length() < 1 ) {
 303             throw new IllegalArgumentException();
 304         }
 305         checkPermission();
 306         configure();
 307         this.pattern = pattern;
 308         this.limit = 0;
 309         this.count = 1;
 310         openFiles();
 311     }
 312 
 313     /**
 314      * Initialize a <tt>FileHandler</tt> to write to the given filename,
 315      * with optional append.
 316      * <p>
 317      * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
 318      * properties (or their default values) except that the given pattern
 319      * argument is used as the filename pattern, the file limit is
 320      * set to no limit, the file count is set to one, and the append
 321      * mode is set to the given <tt>append</tt> argument.
 322      * <p>
 323      * There is no limit on the amount of data that may be written,
 324      * so use this with care.
 325      *
 326      * @param pattern  the name of the output file
 327      * @param append  specifies append mode
 328      * @exception  IOException if there are IO problems opening the files.
 329      * @exception  SecurityException  if a security manager exists and if
 330      *             the caller does not have <tt>LoggingPermission("control")</tt>.
 331      * @exception  IllegalArgumentException if pattern is an empty string
 332      */
 333     public FileHandler(String pattern, boolean append) throws IOException,
 334             SecurityException {
 335         if (pattern.length() < 1 ) {
 336             throw new IllegalArgumentException();
 337         }
 338         checkPermission();
 339         configure();
 340         this.pattern = pattern;
 341         this.limit = 0;
 342         this.count = 1;
 343         this.append = append;
 344         openFiles();
 345     }
 346 
 347     /**
 348      * Initialize a <tt>FileHandler</tt> to write to a set of files.  When
 349      * (approximately) the given limit has been written to one file,
 350      * another file will be opened.  The output will cycle through a set
 351      * of count files.
 352      * <p>
 353      * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
 354      * properties (or their default values) except that the given pattern
 355      * argument is used as the filename pattern, the file limit is
 356      * set to the limit argument, and the file count is set to the
 357      * given count argument.
 358      * <p>
 359      * The count must be at least 1.
 360      *
 361      * @param pattern  the pattern for naming the output file
 362      * @param limit  the maximum number of bytes to write to any one file
 363      * @param count  the number of files to use
 364      * @exception  IOException if there are IO problems opening the files.
 365      * @exception  SecurityException  if a security manager exists and if
 366      *             the caller does not have <tt>LoggingPermission("control")</tt>.
 367      * @exception  IllegalArgumentException if {@code limit < 0}, or {@code count < 1}.
 368      * @exception  IllegalArgumentException if pattern is an empty string
 369      */
 370     public FileHandler(String pattern, int limit, int count)
 371                                         throws IOException, SecurityException {
 372         if (limit < 0 || count < 1 || pattern.length() < 1) {
 373             throw new IllegalArgumentException();
 374         }
 375         checkPermission();
 376         configure();
 377         this.pattern = pattern;
 378         this.limit = limit;
 379         this.count = count;
 380         openFiles();
 381     }
 382 
 383     /**
 384      * Initialize a <tt>FileHandler</tt> to write to a set of files
 385      * with optional append.  When (approximately) the given limit has
 386      * been written to one file, another file will be opened.  The
 387      * output will cycle through a set of count files.
 388      * <p>
 389      * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
 390      * properties (or their default values) except that the given pattern
 391      * argument is used as the filename pattern, the file limit is
 392      * set to the limit argument, and the file count is set to the
 393      * given count argument, and the append mode is set to the given
 394      * <tt>append</tt> argument.
 395      * <p>
 396      * The count must be at least 1.
 397      *
 398      * @param pattern  the pattern for naming the output file
 399      * @param limit  the maximum number of bytes to write to any one file
 400      * @param count  the number of files to use
 401      * @param append  specifies append mode
 402      * @exception  IOException if there are IO problems opening the files.
 403      * @exception  SecurityException  if a security manager exists and if
 404      *             the caller does not have <tt>LoggingPermission("control")</tt>.
 405      * @exception  IllegalArgumentException if {@code limit < 0}, or {@code count < 1}.
 406      * @exception  IllegalArgumentException if pattern is an empty string
 407      *
 408      */
 409     public FileHandler(String pattern, int limit, int count, boolean append)
 410                                         throws IOException, SecurityException {
 411         if (limit < 0 || count < 1 || pattern.length() < 1) {
 412             throw new IllegalArgumentException();
 413         }
 414         checkPermission();
 415         configure();
 416         this.pattern = pattern;
 417         this.limit = limit;
 418         this.count = count;
 419         this.append = append;
 420         openFiles();
 421     }
 422 
 423     private  boolean isParentWritable(Path path) {
 424         Path parent = path.getParent();
 425         if (parent == null) {
 426             parent = path.toAbsolutePath().getParent();
 427         }
 428         return parent != null && Files.isWritable(parent);
 429     }
 430 
 431     /**
 432      * Open the set of output files, based on the configured
 433      * instance variables.
 434      */
 435     private void openFiles() throws IOException {
 436         LogManager manager = LogManager.getLogManager();
 437         manager.checkPermission();
 438         if (count < 1) {
 439            throw new IllegalArgumentException("file count = " + count);
 440         }
 441         if (limit < 0) {
 442             limit = 0;
 443         }
 444 
 445         // We register our own ErrorManager during initialization
 446         // so we can record exceptions.
 447         InitializationErrorManager em = new InitializationErrorManager();
 448         setErrorManager(em);
 449 
 450         // Create a lock file.  This grants us exclusive access
 451         // to our set of output files, as long as we are alive.
 452         int unique = -1;
 453         for (;;) {
 454             unique++;
 455             if (unique > maxLocks) {
 456                 throw new IOException("Couldn't get lock for " + pattern
 457                         + ", maxLocks: " + maxLocks);
 458             }
 459             // Generate a lock file name from the "unique" int.
 460             lockFileName = generate(pattern, 0, unique).toString() + ".lck";
 461             // Now try to lock that filename.
 462             // Because some systems (e.g., Solaris) can only do file locks
 463             // between processes (and not within a process), we first check
 464             // if we ourself already have the file locked.
 465             synchronized(locks) {
 466                 if (locks.contains(lockFileName)) {
 467                     // We already own this lock, for a different FileHandler
 468                     // object.  Try again.
 469                     continue;
 470                 }
 471 
 472                 final Path lockFilePath = Paths.get(lockFileName);
 473                 FileChannel channel = null;
 474                 int retries = -1;
 475                 boolean fileCreated = false;
 476                 while (channel == null && retries++ < 1) {
 477                     try {
 478                         channel = FileChannel.open(lockFilePath,
 479                                 CREATE_NEW, WRITE);
 480                         fileCreated = true;
 481                     } catch (FileAlreadyExistsException ix) {
 482                         // This may be a zombie file left over by a previous
 483                         // execution. Reuse it - but only if we can actually
 484                         // write to its directory.
 485                         // Note that this is a situation that may happen,
 486                         // but not too frequently.
 487                         if (Files.isRegularFile(lockFilePath, LinkOption.NOFOLLOW_LINKS)
 488                             && isParentWritable(lockFilePath)) {
 489                             try {
 490                                 channel = FileChannel.open(lockFilePath,
 491                                     WRITE, APPEND);
 492                             } catch (NoSuchFileException x) {
 493                                 // Race condition - retry once, and if that
 494                                 // fails again just try the next name in
 495                                 // the sequence.
 496                                 continue;
 497                             } catch(IOException x) {
 498                                 // the file may not be writable for us.
 499                                 // try the next name in the sequence
 500                                 break;
 501                             }
 502                         } else {
 503                             // at this point channel should still be null.
 504                             // break and try the next name in the sequence.
 505                             break;
 506                         }
 507                     }
 508                 }
 509 
 510                 if (channel == null) continue; // try the next name;
 511                 lockFileChannel = channel;
 512 
 513                 boolean available;
 514                 try {
 515                     available = lockFileChannel.tryLock() != null;
 516                     // We got the lock OK.
 517                     // At this point we could call File.deleteOnExit().
 518                     // However, this could have undesirable side effects
 519                     // as indicated by JDK-4872014. So we will instead
 520                     // rely on the fact that close() will remove the lock
 521                     // file and that whoever is creating FileHandlers should
 522                     // be responsible for closing them.
 523                 } catch (IOException ix) {
 524                     // We got an IOException while trying to get the lock.
 525                     // This normally indicates that locking is not supported
 526                     // on the target directory.  We have to proceed without
 527                     // getting a lock.   Drop through, but only if we did
 528                     // create the file...
 529                     available = fileCreated;
 530                 } catch (OverlappingFileLockException x) {
 531                     // someone already locked this file in this VM, through
 532                     // some other channel - that is - using something else
 533                     // than new FileHandler(...);
 534                     // continue searching for an available lock.
 535                     available = false;
 536                 }
 537                 if (available) {
 538                     // We got the lock.  Remember it.
 539                     locks.add(lockFileName);
 540                     break;
 541                 }
 542 
 543                 // We failed to get the lock.  Try next file.
 544                 lockFileChannel.close();
 545             }
 546         }
 547 
 548         files = new File[count];
 549         for (int i = 0; i < count; i++) {
 550             files[i] = generate(pattern, i, unique);
 551         }
 552 
 553         // Create the initial log file.
 554         if (append) {
 555             open(files[0], true);
 556         } else {
 557             rotate();
 558         }
 559 
 560         // Did we detect any exceptions during initialization?
 561         Exception ex = em.lastException;
 562         if (ex != null) {
 563             if (ex instanceof IOException) {
 564                 throw (IOException) ex;
 565             } else if (ex instanceof SecurityException) {
 566                 throw (SecurityException) ex;
 567             } else {
 568                 throw new IOException("Exception: " + ex);
 569             }
 570         }
 571 
 572         // Install the normal default ErrorManager.
 573         setErrorManager(new ErrorManager());
 574     }
 575 
 576     /**
 577      * Generate a file based on a user-supplied pattern, generation number,
 578      * and an integer uniqueness suffix
 579      * @param pattern the pattern for naming the output file
 580      * @param generation the generation number to distinguish rotated logs
 581      * @param unique a unique number to resolve conflicts
 582      * @return the generated File
 583      * @throws IOException
 584      */
 585     private File generate(String pattern, int generation, int unique)
 586             throws IOException {
 587         File file = null;
 588         String word = "";
 589         int ix = 0;
 590         boolean sawg = false;
 591         boolean sawu = false;
 592         while (ix < pattern.length()) {
 593             char ch = pattern.charAt(ix);
 594             ix++;
 595             char ch2 = 0;
 596             if (ix < pattern.length()) {
 597                 ch2 = Character.toLowerCase(pattern.charAt(ix));
 598             }
 599             if (ch == '/') {
 600                 if (file == null) {
 601                     file = new File(word);
 602                 } else {
 603                     file = new File(file, word);
 604                 }
 605                 word = "";
 606                 continue;
 607             } else  if (ch == '%') {
 608                 if (ch2 == 't') {
 609                     String tmpDir = System.getProperty("java.io.tmpdir");
 610                     if (tmpDir == null) {
 611                         tmpDir = System.getProperty("user.home");
 612                     }
 613                     file = new File(tmpDir);
 614                     ix++;
 615                     word = "";
 616                     continue;
 617                 } else if (ch2 == 'h') {
 618                     file = new File(System.getProperty("user.home"));
 619                     if (isSetUID()) {
 620                         // Ok, we are in a set UID program.  For safety's sake
 621                         // we disallow attempts to open files relative to %h.
 622                         throw new IOException("can't use %h in set UID program");
 623                     }
 624                     ix++;
 625                     word = "";
 626                     continue;
 627                 } else if (ch2 == 'g') {
 628                     word = word + generation;
 629                     sawg = true;
 630                     ix++;
 631                     continue;
 632                 } else if (ch2 == 'u') {
 633                     word = word + unique;
 634                     sawu = true;
 635                     ix++;
 636                     continue;
 637                 } else if (ch2 == '%') {
 638                     word = word + "%";
 639                     ix++;
 640                     continue;
 641                 }
 642             }
 643             word = word + ch;
 644         }
 645         if (count > 1 && !sawg) {
 646             word = word + "." + generation;
 647         }
 648         if (unique > 0 && !sawu) {
 649             word = word + "." + unique;
 650         }
 651         if (word.length() > 0) {
 652             if (file == null) {
 653                 file = new File(word);
 654             } else {
 655                 file = new File(file, word);
 656             }
 657         }
 658         return file;
 659     }
 660 
 661     /**
 662      * Rotate the set of output files
 663      */
 664     private synchronized void rotate() {
 665         Level oldLevel = getLevel();
 666         setLevel(Level.OFF);
 667 
 668         super.close();
 669         for (int i = count-2; i >= 0; i--) {
 670             File f1 = files[i];
 671             File f2 = files[i+1];
 672             if (f1.exists()) {
 673                 if (f2.exists()) {
 674                     f2.delete();
 675                 }
 676                 f1.renameTo(f2);
 677             }
 678         }
 679         try {
 680             open(files[0], false);
 681         } catch (IOException ix) {
 682             // We don't want to throw an exception here, but we
 683             // report the exception to any registered ErrorManager.
 684             reportError(null, ix, ErrorManager.OPEN_FAILURE);
 685 
 686         }
 687         setLevel(oldLevel);
 688     }
 689 
 690     /**
 691      * Format and publish a <tt>LogRecord</tt>.
 692      *
 693      * @param  record  description of the log event. A null record is
 694      *                 silently ignored and is not published
 695      */
 696     @Override
 697     public synchronized void publish(LogRecord record) {
 698         if (!isLoggable(record)) {
 699             return;
 700         }
 701         super.publish(record);
 702         flush();
 703         if (limit > 0 && meter.written >= limit) {
 704             // We performed access checks in the "init" method to make sure
 705             // we are only initialized from trusted code.  So we assume
 706             // it is OK to write the target files, even if we are
 707             // currently being called from untrusted code.
 708             // So it is safe to raise privilege here.
 709             AccessController.doPrivileged(new PrivilegedAction<Object>() {
 710                 @Override
 711                 public Object run() {
 712                     rotate();
 713                     return null;
 714                 }
 715             });
 716         }
 717     }
 718 
 719     /**
 720      * Close all the files.
 721      *
 722      * @exception  SecurityException  if a security manager exists and if
 723      *             the caller does not have <tt>LoggingPermission("control")</tt>.
 724      */
 725     @Override
 726     public synchronized void close() throws SecurityException {
 727         super.close();
 728         // Unlock any lock file.
 729         if (lockFileName == null) {
 730             return;
 731         }
 732         try {
 733             // Close the lock file channel (which also will free any locks)
 734             lockFileChannel.close();
 735         } catch (Exception ex) {
 736             // Problems closing the stream.  Punt.
 737         }
 738         synchronized(locks) {
 739             locks.remove(lockFileName);
 740         }
 741         new File(lockFileName).delete();
 742         lockFileName = null;
 743         lockFileChannel = null;
 744     }
 745 
 746     private static class InitializationErrorManager extends ErrorManager {
 747         Exception lastException;
 748         @Override
 749         public void error(String msg, Exception ex, int code) {
 750             lastException = ex;
 751         }
 752     }
 753 
 754     /**
 755      * check if we are in a set UID program.
 756      */
 757     private static native boolean isSetUID();
 758 }