1 /* 2 * Copyright (c) 2008, 2019, 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 sun.nio.fs; 27 28 import java.nio.file.*; 29 import java.util.*; 30 import java.io.IOException; 31 import jdk.internal.misc.Unsafe; 32 33 import static sun.nio.fs.UnixConstants.*; 34 35 /** 36 * Solaris implementation of WatchService based on file events notification 37 * facility. 38 */ 39 40 class SolarisWatchService 41 extends AbstractWatchService 42 { 43 private static final Unsafe unsafe = Unsafe.getUnsafe(); 44 private static int addressSize = unsafe.addressSize(); 45 46 private static int dependsArch(int value32, int value64) { 47 return (addressSize == 4) ? value32 : value64; 48 } 49 50 /* 51 * typedef struct port_event { 52 * int portev_events; 53 * ushort_t portev_source; 54 * ushort_t portev_pad; 55 * uintptr_t portev_object; 56 * void *portev_user; 57 * } port_event_t; 58 */ 59 private static final int SIZEOF_PORT_EVENT = dependsArch(16, 24); 60 private static final int OFFSETOF_EVENTS = 0; 61 private static final int OFFSETOF_SOURCE = 4; 62 private static final int OFFSETOF_OBJECT = 8; 63 64 /* 65 * typedef struct file_obj { 66 * timestruc_t fo_atime; 67 * timestruc_t fo_mtime; 68 * timestruc_t fo_ctime; 69 * uintptr_t fo_pad[3]; 70 * char *fo_name; 71 * } file_obj_t; 72 */ 73 private static final int SIZEOF_FILEOBJ = dependsArch(40, 80); 74 private static final int OFFSET_FO_NAME = dependsArch(36, 72); 75 76 // port sources 77 private static final short PORT_SOURCE_USER = 3; 78 private static final short PORT_SOURCE_FILE = 7; 79 80 // user-watchable events 81 private static final int FILE_MODIFIED = 0x00000002; 82 private static final int FILE_ATTRIB = 0x00000004; 83 private static final int FILE_NOFOLLOW = 0x10000000; 84 85 // exception events 86 private static final int FILE_DELETE = 0x00000010; 87 private static final int FILE_RENAME_TO = 0x00000020; 88 private static final int FILE_RENAME_FROM = 0x00000040; 89 private static final int UNMOUNTED = 0x20000000; 90 private static final int MOUNTEDOVER = 0x40000000; 91 92 // background thread to read change events 93 private final Poller poller; 94 95 SolarisWatchService(UnixFileSystem fs) throws IOException { 96 int port = -1; 97 try { 98 port = portCreate(); 99 } catch (UnixException x) { 100 throw new IOException(x.errorString()); 101 } 102 103 this.poller = new Poller(fs, this, port); 104 this.poller.start(); 105 } 106 107 @Override 108 WatchKey register(Path dir, 109 WatchEvent.Kind<?>[] events, 110 WatchEvent.Modifier... modifiers) 111 throws IOException 112 { 113 // delegate to poller 114 return poller.register(dir, events, modifiers); 115 } 116 117 @Override 118 void implClose() throws IOException { 119 // delegate to poller 120 poller.close(); 121 } 122 123 /** 124 * WatchKey implementation 125 */ 126 private class SolarisWatchKey extends AbstractWatchKey 127 implements DirectoryNode 128 { 129 private final UnixFileKey fileKey; 130 131 // pointer to native file_obj object 132 private final long object; 133 134 // events (may be changed). set to null when watch key is invalid 135 private volatile Set<? extends WatchEvent.Kind<?>> events; 136 137 // map of entries in directory; created lazily; accessed only by 138 // poller thread. 139 private Map<Path,EntryNode> children = new HashMap<>(); 140 141 SolarisWatchKey(SolarisWatchService watcher, 142 UnixPath dir, 143 UnixFileKey fileKey, 144 long object, 145 Set<? extends WatchEvent.Kind<?>> events) 146 { 147 super(dir, watcher); 148 this.fileKey = fileKey; 149 this.object = object; 150 this.events = events; 151 } 152 153 UnixPath getDirectory() { 154 return (UnixPath)watchable(); 155 } 156 157 UnixFileKey getFileKey() { 158 return fileKey; 159 } 160 161 @Override 162 public long object() { 163 return object; 164 } 165 166 void invalidate() { 167 events = null; 168 } 169 170 Set<? extends WatchEvent.Kind<?>> events() { 171 return events; 172 } 173 174 void setEvents(Set<? extends WatchEvent.Kind<?>> events) { 175 this.events = events; 176 } 177 178 Map<Path,EntryNode> children() { 179 return children; 180 } 181 182 @Override 183 public boolean isValid() { 184 return events != null; 185 } 186 187 @Override 188 public void cancel() { 189 if (isValid()) { 190 // delegate to poller 191 poller.cancel(this); 192 } 193 } 194 195 @Override 196 public void addChild(Path name, EntryNode node) { 197 children.put(name, node); 198 } 199 200 @Override 201 public void removeChild(Path name) { 202 children.remove(name); 203 } 204 205 @Override 206 public EntryNode getChild(Path name) { 207 return children.get(name); 208 } 209 } 210 211 /** 212 * Background thread to read from port 213 */ 214 private class Poller extends AbstractPoller { 215 216 // maximum number of events to read per call to port_getn 217 private static final int MAX_EVENT_COUNT = 128; 218 219 // events that map to ENTRY_DELETE 220 private static final int FILE_REMOVED = 221 (FILE_DELETE|FILE_RENAME_TO|FILE_RENAME_FROM); 222 223 // events that tell us not to re-associate the object 224 private static final int FILE_EXCEPTION = 225 (FILE_REMOVED|UNMOUNTED|MOUNTEDOVER); 226 227 // address of event buffers (used to receive events with port_getn) 228 private final long bufferAddress; 229 230 private final SolarisWatchService watcher; 231 232 // the I/O port 233 private final int port; 234 235 // maps file key (dev/inode) to WatchKey 236 private final Map<UnixFileKey,SolarisWatchKey> fileKey2WatchKey; 237 238 // maps file_obj object to Node 239 private final Map<Long,Node> object2Node; 240 241 /** 242 * Create a new instance 243 */ 244 Poller(UnixFileSystem fs, SolarisWatchService watcher, int port) { 245 this.watcher = watcher; 246 this.port = port; 247 this.bufferAddress = 248 unsafe.allocateMemory(SIZEOF_PORT_EVENT * MAX_EVENT_COUNT); 249 this.fileKey2WatchKey = new HashMap<UnixFileKey,SolarisWatchKey>(); 250 this.object2Node = new HashMap<Long,Node>(); 251 } 252 253 @Override 254 void wakeup() throws IOException { 255 // write to port to wakeup polling thread 256 try { 257 portSend(port, 0); 258 } catch (UnixException x) { 259 throw new IOException(x.errorString()); 260 } 261 } 262 263 @Override 264 Object implRegister(Path obj, 265 Set<? extends WatchEvent.Kind<?>> events, 266 WatchEvent.Modifier... modifiers) 267 { 268 // no modifiers supported at this time 269 if (modifiers.length > 0) { 270 for (WatchEvent.Modifier modifier: modifiers) { 271 if (modifier == null) 272 return new NullPointerException(); 273 if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) && 274 !ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) && 275 !ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) { 276 return new UnsupportedOperationException("Modifier not supported"); 277 } 278 } 279 } 280 281 UnixPath dir = (UnixPath)obj; 282 283 // check file is directory 284 UnixFileAttributes attrs = null; 285 try { 286 attrs = UnixFileAttributes.get(dir, true); 287 } catch (UnixException x) { 288 return x.asIOException(dir); 289 } 290 if (!attrs.isDirectory()) { 291 return new NotDirectoryException(dir.getPathForExceptionMessage()); 292 } 293 294 // if already registered then update the events and return existing key 295 UnixFileKey fileKey = attrs.fileKey(); 296 SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey); 297 if (watchKey != null) { 298 try { 299 updateEvents(watchKey, events); 300 } catch (UnixException x) { 301 return x.asIOException(dir); 302 } 303 return watchKey; 304 } 305 306 // register directory 307 long object = 0L; 308 try { 309 object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB)); 310 } catch (UnixException x) { 311 return x.asIOException(dir); 312 } 313 314 // create watch key and insert it into maps 315 watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events); 316 object2Node.put(object, watchKey); 317 fileKey2WatchKey.put(fileKey, watchKey); 318 319 // register all entries in directory 320 registerChildren(dir, watchKey, false, false); 321 322 return watchKey; 323 } 324 325 // release resources for single entry 326 void releaseChild(EntryNode node) { 327 long object = node.object(); 328 if (object != 0L) { 329 object2Node.remove(object); 330 releaseObject(object, true); 331 node.setObject(0L); 332 } 333 } 334 335 // release resources for entries in directory 336 void releaseChildren(SolarisWatchKey key) { 337 for (EntryNode node: key.children().values()) { 338 releaseChild(node); 339 } 340 } 341 342 // cancel single key 343 @Override 344 void implCancelKey(WatchKey obj) { 345 SolarisWatchKey key = (SolarisWatchKey)obj; 346 if (key.isValid()) { 347 fileKey2WatchKey.remove(key.getFileKey()); 348 349 // release resources for entries 350 releaseChildren(key); 351 352 // release resources for directory 353 long object = key.object(); 354 object2Node.remove(object); 355 releaseObject(object, true); 356 357 // and finally invalidate the key 358 key.invalidate(); 359 } 360 } 361 362 // close watch service 363 @Override 364 void implCloseAll() { 365 // release all native resources 366 for (Long object: object2Node.keySet()) { 367 releaseObject(object, true); 368 } 369 370 // invalidate all keys 371 for (Map.Entry<UnixFileKey,SolarisWatchKey> entry: fileKey2WatchKey.entrySet()) { 372 entry.getValue().invalidate(); 373 } 374 375 // clean-up 376 object2Node.clear(); 377 fileKey2WatchKey.clear(); 378 379 // free global resources 380 unsafe.freeMemory(bufferAddress); 381 UnixNativeDispatcher.close(port); 382 } 383 384 /** 385 * Poller main loop. Blocks on port_getn waiting for events and then 386 * processes them. 387 */ 388 @Override 389 public void run() { 390 try { 391 for (;;) { 392 int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT); 393 assert n > 0; 394 395 long address = bufferAddress; 396 for (int i=0; i<n; i++) { 397 boolean shutdown = processEvent(address); 398 if (shutdown) 399 return; 400 address += SIZEOF_PORT_EVENT; 401 } 402 } 403 } catch (UnixException x) { 404 x.printStackTrace(); 405 } 406 } 407 408 /** 409 * Process a single port_event 410 * 411 * Returns true if poller thread is requested to shutdown. 412 */ 413 boolean processEvent(long address) { 414 // pe->portev_source 415 short source = unsafe.getShort(address + OFFSETOF_SOURCE); 416 // pe->portev_object 417 long object = unsafe.getAddress(address + OFFSETOF_OBJECT); 418 // pe->portev_events 419 int events = unsafe.getInt(address + OFFSETOF_EVENTS); 420 421 // user event is trigger to process pending requests 422 if (source != PORT_SOURCE_FILE) { 423 if (source == PORT_SOURCE_USER) { 424 // process any pending requests 425 boolean shutdown = processRequests(); 426 if (shutdown) 427 return true; 428 } 429 return false; 430 } 431 432 // lookup object to get Node 433 Node node = object2Node.get(object); 434 if (node == null) { 435 // should not happen 436 return false; 437 } 438 439 // As a workaround for 6642290 and 6636438/6636412 we don't use 440 // FILE_EXCEPTION events to tell use not to register the file. 441 // boolean reregister = (events & FILE_EXCEPTION) == 0; 442 boolean reregister = true; 443 444 // If node is EntryNode then event relates to entry in directory 445 // If node is a SolarisWatchKey (DirectoryNode) then event relates 446 // to a watched directory. 447 boolean isDirectory = (node instanceof SolarisWatchKey); 448 if (isDirectory) { 449 processDirectoryEvents((SolarisWatchKey)node, events); 450 } else { 451 boolean ignore = processEntryEvents((EntryNode)node, events); 452 if (ignore) 453 reregister = false; 454 } 455 456 // need to re-associate to get further events 457 if (reregister) { 458 try { 459 events = FILE_MODIFIED | FILE_ATTRIB; 460 if (!isDirectory) events |= FILE_NOFOLLOW; 461 portAssociate(port, 462 PORT_SOURCE_FILE, 463 object, 464 events); 465 } catch (UnixException x) { 466 // unable to re-register 467 reregister = false; 468 } 469 } 470 471 // object is not re-registered so release resources. If 472 // object is a watched directory then signal key 473 if (!reregister) { 474 // release resources 475 object2Node.remove(object); 476 releaseObject(object, false); 477 478 // if watch key then signal it 479 if (isDirectory) { 480 SolarisWatchKey key = (SolarisWatchKey)node; 481 fileKey2WatchKey.remove( key.getFileKey() ); 482 key.invalidate(); 483 key.signal(); 484 } else { 485 // if entry then remove it from parent 486 EntryNode entry = (EntryNode)node; 487 SolarisWatchKey key = (SolarisWatchKey)entry.parent(); 488 key.removeChild(entry.name()); 489 } 490 } 491 492 return false; 493 } 494 495 /** 496 * Process directory events. If directory is modified then re-scan 497 * directory to register any new entries 498 */ 499 void processDirectoryEvents(SolarisWatchKey key, int mask) { 500 if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) { 501 registerChildren(key.getDirectory(), key, 502 key.events().contains(StandardWatchEventKinds.ENTRY_CREATE), 503 key.events().contains(StandardWatchEventKinds.ENTRY_DELETE)); 504 } 505 } 506 507 /** 508 * Process events for entries in registered directories. Returns {@code 509 * true} if events are ignored because the watch key has been cancelled. 510 */ 511 boolean processEntryEvents(EntryNode node, int mask) { 512 SolarisWatchKey key = (SolarisWatchKey)node.parent(); 513 Set<? extends WatchEvent.Kind<?>> events = key.events(); 514 if (events == null) { 515 // key has been cancelled so ignore event 516 return true; 517 } 518 519 // entry modified 520 if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) && 521 events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) 522 { 523 key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name()); 524 } 525 526 527 return false; 528 } 529 530 /** 531 * Registers all entries in the given directory 532 * 533 * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters 534 * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued 535 * when new entries are found. When initially registering a directory 536 * they will always be false. When re-scanning a directory then it 537 * depends on if the events are enabled or not. 538 */ 539 void registerChildren(UnixPath dir, 540 SolarisWatchKey parent, 541 boolean sendCreateEvents, 542 boolean sendDeleteEvents) 543 { 544 boolean isModifyEnabled = 545 parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ; 546 547 // reset visited flag on entries so that we can detect file deletes 548 for (EntryNode node: parent.children().values()) { 549 node.setVisited(false); 550 } 551 552 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 553 for (Path entry: stream) { 554 Path name = entry.getFileName(); 555 556 // skip entry if already registered 557 EntryNode node = parent.getChild(name); 558 if (node != null) { 559 node.setVisited(true); 560 continue; 561 } 562 563 // new entry found 564 565 long object = 0L; 566 int errno = 0; 567 boolean addNode = false; 568 569 // if ENTRY_MODIFY enabled then we register the entry for events 570 if (isModifyEnabled) { 571 try { 572 UnixPath path = (UnixPath)entry; 573 int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); 574 object = registerImpl(path, events); 575 addNode = true; 576 } catch (UnixException x) { 577 errno = x.errno(); 578 } 579 } else { 580 addNode = true; 581 } 582 583 if (addNode) { 584 // create node 585 node = new EntryNode(object, (UnixPath)entry.getFileName(), parent); 586 node.setVisited(true); 587 // tell the parent about it 588 parent.addChild(entry.getFileName(), node); 589 if (object != 0L) 590 object2Node.put(object, node); 591 } 592 593 // send ENTRY_CREATE event for the new file 594 // send ENTRY_DELETE event for files that were deleted immediately 595 boolean deleted = (errno == ENOENT); 596 if (sendCreateEvents && (addNode || deleted)) 597 parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name); 598 if (sendDeleteEvents && deleted) 599 parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name); 600 601 } 602 } catch (DirectoryIteratorException | IOException x) { 603 // queue OVERFLOW event so that user knows to re-scan directory 604 parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null); 605 return; 606 } 607 608 // clean-up and send ENTRY_DELETE events for any entries that were 609 // not found 610 Iterator<Map.Entry<Path,EntryNode>> iterator = 611 parent.children().entrySet().iterator(); 612 while (iterator.hasNext()) { 613 Map.Entry<Path,EntryNode> entry = iterator.next(); 614 EntryNode node = entry.getValue(); 615 if (!node.isVisited()) { 616 long object = node.object(); 617 if (object != 0L) { 618 object2Node.remove(object); 619 releaseObject(object, true); 620 } 621 if (sendDeleteEvents) 622 parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name()); 623 iterator.remove(); 624 } 625 } 626 } 627 628 /** 629 * Update watch key's events. If ENTRY_MODIFY changes to be enabled 630 * then register each file in the directory; If ENTRY_MODIFY changed to 631 * be disabled then unregister each file. 632 */ 633 void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events) 634 throws UnixException 635 { 636 637 // update events, remembering if ENTRY_MODIFY was previously 638 // enabled or disabled. 639 boolean oldModifyEnabled = key.events() 640 .contains(StandardWatchEventKinds.ENTRY_MODIFY); 641 key.setEvents(events); 642 643 // check if ENTRY_MODIFY has changed 644 boolean newModifyEnabled = events 645 .contains(StandardWatchEventKinds.ENTRY_MODIFY); 646 if (newModifyEnabled != oldModifyEnabled) { 647 UnixException ex = null; 648 for (EntryNode node: key.children().values()) { 649 if (newModifyEnabled) { 650 // register 651 UnixPath path = key.getDirectory().resolve(node.name()); 652 int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); 653 try { 654 long object = registerImpl(path, ev); 655 object2Node.put(object, node); 656 node.setObject(object); 657 } catch (UnixException x) { 658 // if file has been deleted then it will be detected 659 // as a FILE_MODIFIED event on the directory 660 if (x.errno() != ENOENT) { 661 ex = x; 662 break; 663 } 664 } 665 } else { 666 // unregister 667 releaseChild(node); 668 } 669 } 670 671 // an error occurred 672 if (ex != null) { 673 releaseChildren(key); 674 throw ex; 675 } 676 } 677 } 678 679 /** 680 * Calls port_associate to register the given path. 681 * Returns pointer to fileobj structure that is allocated for 682 * the registration. 683 */ 684 long registerImpl(UnixPath dir, int events) 685 throws UnixException 686 { 687 // allocate memory for the path (file_obj->fo_name field) 688 byte[] path = dir.getByteArrayForSysCalls(); 689 int len = path.length; 690 long name = unsafe.allocateMemory(len+1); 691 unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, 692 name, (long)len); 693 unsafe.putByte(name + len, (byte)0); 694 695 // allocate memory for filedatanode structure - this is the object 696 // to port_associate 697 long object = unsafe.allocateMemory(SIZEOF_FILEOBJ); 698 unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0); 699 unsafe.putAddress(object + OFFSET_FO_NAME, name); 700 701 // associate the object with the port 702 try { 703 portAssociate(port, 704 PORT_SOURCE_FILE, 705 object, 706 events); 707 } catch (UnixException x) { 708 // debugging 709 if (x.errno() == EAGAIN) { 710 System.err.println("The maximum number of objects associated "+ 711 "with the port has been reached"); 712 } 713 714 unsafe.freeMemory(name); 715 unsafe.freeMemory(object); 716 throw x; 717 } 718 return object; 719 } 720 721 /** 722 * Frees all resources for an file_obj object; optionally remove 723 * association from port 724 */ 725 void releaseObject(long object, boolean dissociate) { 726 // remove association 727 if (dissociate) { 728 try { 729 portDissociate(port, PORT_SOURCE_FILE, object); 730 } catch (UnixException x) { 731 // ignore 732 } 733 } 734 735 // free native memory 736 long name = unsafe.getAddress(object + OFFSET_FO_NAME); 737 unsafe.freeMemory(name); 738 unsafe.freeMemory(object); 739 } 740 } 741 742 /** 743 * A node with native (file_obj) resources 744 */ 745 private static interface Node { 746 long object(); 747 } 748 749 /** 750 * A directory node with a map of the entries in the directory 751 */ 752 private static interface DirectoryNode extends Node { 753 void addChild(Path name, EntryNode node); 754 void removeChild(Path name); 755 EntryNode getChild(Path name); 756 } 757 758 /** 759 * An implementation of a node that is an entry in a directory. 760 */ 761 private static class EntryNode implements Node { 762 private long object; 763 private final UnixPath name; 764 private final DirectoryNode parent; 765 private boolean visited; 766 767 EntryNode(long object, UnixPath name, DirectoryNode parent) { 768 this.object = object; 769 this.name = name; 770 this.parent = parent; 771 } 772 773 @Override 774 public long object() { 775 return object; 776 } 777 778 void setObject(long ptr) { 779 this.object = ptr; 780 } 781 782 UnixPath name() { 783 return name; 784 } 785 786 DirectoryNode parent() { 787 return parent; 788 } 789 790 boolean isVisited() { 791 return visited; 792 } 793 794 void setVisited(boolean v) { 795 this.visited = v; 796 } 797 } 798 799 // -- native methods -- 800 801 private static native void init(); 802 803 private static native int portCreate() throws UnixException; 804 805 private static native void portAssociate(int port, int source, long object, int events) 806 throws UnixException; 807 808 private static native void portDissociate(int port, int source, long object) 809 throws UnixException; 810 811 private static native void portSend(int port, int events) 812 throws UnixException; 813 814 private static native int portGetn(int port, long address, int max) 815 throws UnixException; 816 817 static { 818 jdk.internal.access.SharedSecrets.getJavaLangAccess() 819 .loadLibrary(SolarisWatchService.class, "nio"); 820 init(); 821 } 822 }