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><handler-name></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> <handler-name>.level 74 * specifies the default level for the <tt>Handler</tt> 75 * (defaults to <tt>Level.ALL</tt>). </li> 76 * <li> <handler-name>.filter 77 * specifies the name of a <tt>Filter</tt> class to use 78 * (defaults to no <tt>Filter</tt>). </li> 79 * <li> <handler-name>.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> <handler-name>.encoding 83 * the name of the character set encoding to use (defaults to 84 * the default platform encoding). </li> 85 * <li> <handler-name>.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> <handler-name>.count 90 * specifies how many output files to cycle through (defaults to 1). </li> 91 * <li> <handler-name>.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> <handler-name>.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 }