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