FileHandler.java revision 4ff539dbc5a809ef3eacbe2d9f2b97f640b7e9cf
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
27package java.util.logging;
28
29import static java.nio.file.StandardOpenOption.APPEND;
30import static java.nio.file.StandardOpenOption.CREATE_NEW;
31import static java.nio.file.StandardOpenOption.WRITE;
32
33import java.io.BufferedOutputStream;
34import java.io.File;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.OutputStream;
38import java.nio.channels.FileChannel;
39import java.nio.channels.OverlappingFileLockException;
40import java.nio.file.FileAlreadyExistsException;
41import java.nio.file.Files;
42import java.nio.file.LinkOption;
43import java.nio.file.NoSuchFileException;
44import java.nio.file.Path;
45import java.nio.file.Paths;
46import java.security.AccessController;
47import java.security.PrivilegedAction;
48import java.util.HashSet;
49import 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
151public 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
172        MeteredStream(OutputStream out, int written) {
173            this.out = out;
174            this.written = written;
175        }
176
177        @Override
178        public void write(int b) throws IOException {
179            out.write(b);
180            written++;
181        }
182
183        @Override
184        public void write(byte buff[]) throws IOException {
185            out.write(buff);
186            written += buff.length;
187        }
188
189        @Override
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
196        public void flush() throws IOException {
197            out.flush();
198        }
199
200        @Override
201        public void close() throws IOException {
202            out.close();
203        }
204    }
205
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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
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
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
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