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><handler-name></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> <handler-name>.level 75 * specifies the default level for the <tt>Handler</tt> 76 * (defaults to <tt>Level.ALL</tt>). </li> 77 * <li> <handler-name>.filter 78 * specifies the name of a <tt>Filter</tt> class to use 79 * (defaults to no <tt>Filter</tt>). </li> 80 * <li> <handler-name>.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> <handler-name>.encoding 84 * the name of the character set encoding to use (defaults to 85 * the default platform encoding). </li> 86 * <li> <handler-name>.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> <handler-name>.count 91 * specifies how many output files to cycle through (defaults to 1). </li> 92 * <li> <handler-name>.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> <handler-name>.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-removed: Don't prohibit using user.home property in 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 */ 609 ix++; 610 word = ""; 611 continue; 612 } else if (ch2 == 'g') { 613 word = word + generation; 614 sawg = true; 615 ix++; 616 continue; 617 } else if (ch2 == 'u') { 618 word = word + unique; 619 sawu = true; 620 ix++; 621 continue; 622 } else if (ch2 == '%') { 623 word = word + "%"; 624 ix++; 625 continue; 626 } 627 } 628 word = word + ch; 629 } 630 if (count > 1 && !sawg) { 631 word = word + "." + generation; 632 } 633 if (unique > 0 && !sawu) { 634 word = word + "." + unique; 635 } 636 if (word.length() > 0) { 637 if (file == null) { 638 file = new File(word); 639 } else { 640 file = new File(file, word); 641 } 642 } 643 return file; 644 } 645 646 /** 647 * Rotate the set of output files 648 */ rotate()649 private synchronized void rotate() { 650 Level oldLevel = getLevel(); 651 setLevel(Level.OFF); 652 653 super.close(); 654 for (int i = count-2; i >= 0; i--) { 655 File f1 = files[i]; 656 File f2 = files[i+1]; 657 if (f1.exists()) { 658 if (f2.exists()) { 659 f2.delete(); 660 } 661 f1.renameTo(f2); 662 } 663 } 664 try { 665 open(files[0], false); 666 } catch (IOException ix) { 667 // We don't want to throw an exception here, but we 668 // report the exception to any registered ErrorManager. 669 reportError(null, ix, ErrorManager.OPEN_FAILURE); 670 671 } 672 setLevel(oldLevel); 673 } 674 675 /** 676 * Format and publish a <tt>LogRecord</tt>. 677 * 678 * @param record description of the log event. A null record is 679 * silently ignored and is not published 680 */ 681 @Override publish(LogRecord record)682 public synchronized void publish(LogRecord record) { 683 if (!isLoggable(record)) { 684 return; 685 } 686 super.publish(record); 687 flush(); 688 if (limit > 0 && meter.written >= limit) { 689 // We performed access checks in the "init" method to make sure 690 // we are only initialized from trusted code. So we assume 691 // it is OK to write the target files, even if we are 692 // currently being called from untrusted code. 693 // So it is safe to raise privilege here. 694 AccessController.doPrivileged(new PrivilegedAction<Object>() { 695 @Override 696 public Object run() { 697 rotate(); 698 return null; 699 } 700 }); 701 } 702 } 703 704 /** 705 * Close all the files. 706 * 707 * @exception SecurityException if a security manager exists and if 708 * the caller does not have <tt>LoggingPermission("control")</tt>. 709 */ 710 @Override close()711 public synchronized void close() throws SecurityException { 712 super.close(); 713 // Unlock any lock file. 714 if (lockFileName == null) { 715 return; 716 } 717 try { 718 // Close the lock file channel (which also will free any locks) 719 lockFileChannel.close(); 720 } catch (Exception ex) { 721 // Problems closing the stream. Punt. 722 } 723 synchronized(locks) { 724 locks.remove(lockFileName); 725 } 726 new File(lockFileName).delete(); 727 lockFileName = null; 728 lockFileChannel = null; 729 } 730 731 private static class InitializationErrorManager extends ErrorManager { 732 Exception lastException; 733 @Override error(String msg, Exception ex, int code)734 public void error(String msg, Exception ex, int code) { 735 lastException = ex; 736 } 737 } 738 739 // Android-removed: isSetUID's only caller is removed. 740 /* 741 /** 742 * check if we are in a set UID program. 743 * 744 private static native boolean isSetUID(); 745 */ 746 } 747