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