1 /* 2 * Copyright (c) 2008, 2016, 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.io.IOException; 29 import java.nio.file.NotDirectoryException; 30 import java.nio.file.Path; 31 import java.nio.file.StandardWatchEventKinds; 32 import java.nio.file.WatchEvent; 33 import java.nio.file.WatchKey; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Set; 37 38 import jdk.internal.misc.Unsafe; 39 40 import static sun.nio.fs.WindowsNativeDispatcher.*; 41 import static sun.nio.fs.WindowsConstants.*; 42 43 /* 44 * Win32 implementation of WatchService based on ReadDirectoryChangesW. 45 */ 46 47 class WindowsWatchService 48 extends AbstractWatchService 49 { 50 private static final int WAKEUP_COMPLETION_KEY = 0; 51 52 // background thread to service I/O completion port 53 private final Poller poller; 54 55 /** 56 * Creates an I/O completion port and a daemon thread to service it 57 */ WindowsWatchService(WindowsFileSystem fs)58 WindowsWatchService(WindowsFileSystem fs) throws IOException { 59 // create I/O completion port 60 long port = 0L; 61 try { 62 port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0); 63 } catch (WindowsException x) { 64 throw new IOException(x.getMessage()); 65 } 66 67 this.poller = new Poller(fs, this, port); 68 this.poller.start(); 69 } 70 71 @Override register(Path path, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)72 WatchKey register(Path path, 73 WatchEvent.Kind<?>[] events, 74 WatchEvent.Modifier... modifiers) 75 throws IOException 76 { 77 // delegate to poller 78 return poller.register(path, events, modifiers); 79 } 80 81 @Override implClose()82 void implClose() throws IOException { 83 // delegate to poller 84 poller.close(); 85 } 86 87 /** 88 * Windows implementation of WatchKey. 89 */ 90 private static class WindowsWatchKey extends AbstractWatchKey { 91 // file key (used to detect existing registrations) 92 private final FileKey fileKey; 93 94 // handle to directory 95 private volatile long handle = INVALID_HANDLE_VALUE; 96 97 // interest events 98 private Set<? extends WatchEvent.Kind<?>> events; 99 100 // subtree 101 private boolean watchSubtree; 102 103 // buffer for change events 104 private NativeBuffer buffer; 105 106 // pointer to bytes returned (in buffer) 107 private long countAddress; 108 109 // pointer to overlapped structure (in buffer) 110 private long overlappedAddress; 111 112 // completion key (used to map I/O completion to WatchKey) 113 private int completionKey; 114 115 // flag indicates that ReadDirectoryChangesW failed 116 // and overlapped I/O operation wasn't started 117 private boolean errorStartingOverlapped; 118 WindowsWatchKey(Path dir, AbstractWatchService watcher, FileKey fileKey)119 WindowsWatchKey(Path dir, 120 AbstractWatchService watcher, 121 FileKey fileKey) 122 { 123 super(dir, watcher); 124 this.fileKey = fileKey; 125 } 126 init(long handle, Set<? extends WatchEvent.Kind<?>> events, boolean watchSubtree, NativeBuffer buffer, long countAddress, long overlappedAddress, int completionKey)127 WindowsWatchKey init(long handle, 128 Set<? extends WatchEvent.Kind<?>> events, 129 boolean watchSubtree, 130 NativeBuffer buffer, 131 long countAddress, 132 long overlappedAddress, 133 int completionKey) 134 { 135 this.handle = handle; 136 this.events = events; 137 this.watchSubtree = watchSubtree; 138 this.buffer = buffer; 139 this.countAddress = countAddress; 140 this.overlappedAddress = overlappedAddress; 141 this.completionKey = completionKey; 142 return this; 143 } 144 handle()145 long handle() { 146 return handle; 147 } 148 events()149 Set<? extends WatchEvent.Kind<?>> events() { 150 return events; 151 } 152 setEvents(Set<? extends WatchEvent.Kind<?>> events)153 void setEvents(Set<? extends WatchEvent.Kind<?>> events) { 154 this.events = events; 155 } 156 watchSubtree()157 boolean watchSubtree() { 158 return watchSubtree; 159 } 160 buffer()161 NativeBuffer buffer() { 162 return buffer; 163 } 164 countAddress()165 long countAddress() { 166 return countAddress; 167 } 168 overlappedAddress()169 long overlappedAddress() { 170 return overlappedAddress; 171 } 172 fileKey()173 FileKey fileKey() { 174 return fileKey; 175 } 176 completionKey()177 int completionKey() { 178 return completionKey; 179 } 180 setErrorStartingOverlapped(boolean value)181 void setErrorStartingOverlapped(boolean value) { 182 errorStartingOverlapped = value; 183 } 184 isErrorStartingOverlapped()185 boolean isErrorStartingOverlapped() { 186 return errorStartingOverlapped; 187 } 188 189 // Invalidate the key, assumes that resources have been released invalidate()190 void invalidate() { 191 ((WindowsWatchService)watcher()).poller.releaseResources(this); 192 handle = INVALID_HANDLE_VALUE; 193 buffer = null; 194 countAddress = 0; 195 overlappedAddress = 0; 196 errorStartingOverlapped = false; 197 } 198 199 @Override isValid()200 public boolean isValid() { 201 return handle != INVALID_HANDLE_VALUE; 202 } 203 204 @Override cancel()205 public void cancel() { 206 if (isValid()) { 207 // delegate to poller 208 ((WindowsWatchService)watcher()).poller.cancel(this); 209 } 210 } 211 } 212 213 // file key to unique identify (open) directory 214 private static class FileKey { 215 private final int volSerialNumber; 216 private final int fileIndexHigh; 217 private final int fileIndexLow; 218 FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow)219 FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow) { 220 this.volSerialNumber = volSerialNumber; 221 this.fileIndexHigh = fileIndexHigh; 222 this.fileIndexLow = fileIndexLow; 223 } 224 225 @Override hashCode()226 public int hashCode() { 227 return volSerialNumber ^ fileIndexHigh ^ fileIndexLow; 228 } 229 230 @Override equals(Object obj)231 public boolean equals(Object obj) { 232 if (obj == this) 233 return true; 234 if (!(obj instanceof FileKey)) 235 return false; 236 FileKey other = (FileKey)obj; 237 if (this.volSerialNumber != other.volSerialNumber) return false; 238 if (this.fileIndexHigh != other.fileIndexHigh) return false; 239 return this.fileIndexLow == other.fileIndexLow; 240 } 241 } 242 243 // all change events 244 private static final int ALL_FILE_NOTIFY_EVENTS = 245 FILE_NOTIFY_CHANGE_FILE_NAME | 246 FILE_NOTIFY_CHANGE_DIR_NAME | 247 FILE_NOTIFY_CHANGE_ATTRIBUTES | 248 FILE_NOTIFY_CHANGE_SIZE | 249 FILE_NOTIFY_CHANGE_LAST_WRITE | 250 FILE_NOTIFY_CHANGE_CREATION | 251 FILE_NOTIFY_CHANGE_SECURITY; 252 253 /** 254 * Background thread to service I/O completion port. 255 */ 256 private static class Poller extends AbstractPoller { 257 private static final Unsafe UNSAFE = Unsafe.getUnsafe(); 258 259 /* 260 * typedef struct _OVERLAPPED { 261 * ULONG_PTR Internal; 262 * ULONG_PTR InternalHigh; 263 * union { 264 * struct { DWORD Offset; DWORD OffsetHigh; }; 265 * PVOID Pointer; 266 * }; 267 * HANDLE hEvent; 268 * } OVERLAPPED; 269 */ 270 private static final short SIZEOF_DWORD = 4; 271 private static final short SIZEOF_OVERLAPPED = 32; // 20 on 32-bit 272 private static final short OFFSETOF_HEVENT = 273 (UNSAFE.addressSize() == 4) ? (short) 16 : 24; 274 275 276 /* 277 * typedef struct _FILE_NOTIFY_INFORMATION { 278 * DWORD NextEntryOffset; 279 * DWORD Action; 280 * DWORD FileNameLength; 281 * WCHAR FileName[1]; 282 * } FileNameLength; 283 */ 284 private static final short OFFSETOF_NEXTENTRYOFFSET = 0; 285 private static final short OFFSETOF_ACTION = 4; 286 private static final short OFFSETOF_FILENAMELENGTH = 8; 287 private static final short OFFSETOF_FILENAME = 12; 288 289 // size of per-directory buffer for events (FIXME - make this configurable) 290 // Need to be less than 4*16384 = 65536. DWORD align. 291 private static final int CHANGES_BUFFER_SIZE = 16 * 1024; 292 293 private final WindowsFileSystem fs; 294 private final WindowsWatchService watcher; 295 private final long port; 296 297 // maps completion key to WatchKey 298 private final Map<Integer, WindowsWatchKey> ck2key; 299 300 // maps file key to WatchKey 301 private final Map<FileKey, WindowsWatchKey> fk2key; 302 303 // unique completion key for each directory 304 // native completion key capacity is 64 bits on Win64. 305 private int lastCompletionKey; 306 Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port)307 Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port) { 308 this.fs = fs; 309 this.watcher = watcher; 310 this.port = port; 311 this.ck2key = new HashMap<>(); 312 this.fk2key = new HashMap<>(); 313 this.lastCompletionKey = 0; 314 } 315 316 @Override wakeup()317 void wakeup() throws IOException { 318 try { 319 PostQueuedCompletionStatus(port, WAKEUP_COMPLETION_KEY); 320 } catch (WindowsException x) { 321 throw new IOException(x.getMessage()); 322 } 323 } 324 325 /** 326 * Register a directory for changes as follows: 327 * 328 * 1. Open directory 329 * 2. Read its attributes (and check it really is a directory) 330 * 3. Assign completion key and associated handle with completion port 331 * 4. Call ReadDirectoryChangesW to start (async) read of changes 332 * 5. Create or return existing key representing registration 333 */ 334 @Override implRegister(Path obj, Set<? extends WatchEvent.Kind<?>> events, WatchEvent.Modifier... modifiers)335 Object implRegister(Path obj, 336 Set<? extends WatchEvent.Kind<?>> events, 337 WatchEvent.Modifier... modifiers) 338 { 339 WindowsPath dir = (WindowsPath)obj; 340 boolean watchSubtree = false; 341 342 // FILE_TREE modifier allowed 343 for (WatchEvent.Modifier modifier: modifiers) { 344 if (ExtendedOptions.FILE_TREE.matches(modifier)) { 345 watchSubtree = true; 346 } else { 347 if (modifier == null) 348 return new NullPointerException(); 349 if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) && 350 !ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) && 351 !ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) { 352 return new UnsupportedOperationException("Modifier not supported"); 353 } 354 } 355 } 356 357 // open directory 358 long handle; 359 try { 360 handle = CreateFile(dir.getPathForWin32Calls(), 361 FILE_LIST_DIRECTORY, 362 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), 363 OPEN_EXISTING, 364 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED); 365 } catch (WindowsException x) { 366 return x.asIOException(dir); 367 } 368 369 boolean registered = false; 370 try { 371 // read attributes and check file is a directory 372 WindowsFileAttributes attrs; 373 try { 374 attrs = WindowsFileAttributes.readAttributes(handle); 375 } catch (WindowsException x) { 376 return x.asIOException(dir); 377 } 378 if (!attrs.isDirectory()) { 379 return new NotDirectoryException(dir.getPathForExceptionMessage()); 380 } 381 382 // check if this directory is already registered 383 FileKey fk = new FileKey(attrs.volSerialNumber(), 384 attrs.fileIndexHigh(), 385 attrs.fileIndexLow()); 386 WindowsWatchKey existing = fk2key.get(fk); 387 388 // if already registered and we're not changing the subtree 389 // modifier then simply update the event and return the key. 390 if (existing != null && watchSubtree == existing.watchSubtree()) { 391 existing.setEvents(events); 392 return existing; 393 } 394 395 // Can overflow the int type capacity. 396 // Skip WAKEUP_COMPLETION_KEY value. 397 int completionKey = ++lastCompletionKey; 398 if (completionKey == WAKEUP_COMPLETION_KEY) 399 completionKey = ++lastCompletionKey; 400 401 // associate handle with completion port 402 try { 403 CreateIoCompletionPort(handle, port, completionKey); 404 } catch (WindowsException x) { 405 return new IOException(x.getMessage()); 406 } 407 408 // allocate memory for events, including space for other structures 409 // needed to do overlapped I/O 410 int size = CHANGES_BUFFER_SIZE + SIZEOF_DWORD + SIZEOF_OVERLAPPED; 411 NativeBuffer buffer = NativeBuffers.getNativeBuffer(size); 412 413 long bufferAddress = buffer.address(); 414 long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED; 415 long countAddress = overlappedAddress - SIZEOF_DWORD; 416 417 // zero the overlapped structure 418 UNSAFE.setMemory(overlappedAddress, SIZEOF_OVERLAPPED, (byte)0); 419 420 // start async read of changes to directory 421 try { 422 createAndAttachEvent(overlappedAddress); 423 424 ReadDirectoryChangesW(handle, 425 bufferAddress, 426 CHANGES_BUFFER_SIZE, 427 watchSubtree, 428 ALL_FILE_NOTIFY_EVENTS, 429 countAddress, 430 overlappedAddress); 431 } catch (WindowsException x) { 432 closeAttachedEvent(overlappedAddress); 433 buffer.release(); 434 return new IOException(x.getMessage()); 435 } 436 437 WindowsWatchKey watchKey; 438 if (existing == null) { 439 // not registered so create new watch key 440 watchKey = new WindowsWatchKey(dir, watcher, fk) 441 .init(handle, events, watchSubtree, buffer, countAddress, 442 overlappedAddress, completionKey); 443 // map file key to watch key 444 fk2key.put(fk, watchKey); 445 } else { 446 // directory already registered so need to: 447 // 1. remove mapping from old completion key to existing watch key 448 // 2. release existing key's resources (handle/buffer) 449 // 3. re-initialize key with new handle/buffer 450 ck2key.remove(existing.completionKey()); 451 releaseResources(existing); 452 watchKey = existing.init(handle, events, watchSubtree, buffer, 453 countAddress, overlappedAddress, completionKey); 454 } 455 // map completion map to watch key 456 ck2key.put(completionKey, watchKey); 457 458 registered = true; 459 return watchKey; 460 461 } finally { 462 if (!registered) CloseHandle(handle); 463 } 464 } 465 466 /** 467 * Cancels the outstanding I/O operation on the directory 468 * associated with the given key and releases the associated 469 * resources. 470 */ releaseResources(WindowsWatchKey key)471 private void releaseResources(WindowsWatchKey key) { 472 if (!key.isErrorStartingOverlapped()) { 473 try { 474 CancelIo(key.handle()); 475 GetOverlappedResult(key.handle(), key.overlappedAddress()); 476 } catch (WindowsException expected) { 477 // expected as I/O operation has been cancelled 478 } 479 } 480 CloseHandle(key.handle()); 481 closeAttachedEvent(key.overlappedAddress()); 482 key.buffer().free(); 483 } 484 485 /** 486 * Creates an unnamed event and set it as the hEvent field 487 * in the given OVERLAPPED structure 488 */ createAndAttachEvent(long ov)489 private void createAndAttachEvent(long ov) throws WindowsException { 490 long hEvent = CreateEvent(false, false); 491 UNSAFE.putAddress(ov + OFFSETOF_HEVENT, hEvent); 492 } 493 494 /** 495 * Closes the event attached to the given OVERLAPPED structure. A 496 * no-op if there isn't an event attached. 497 */ closeAttachedEvent(long ov)498 private void closeAttachedEvent(long ov) { 499 long hEvent = UNSAFE.getAddress(ov + OFFSETOF_HEVENT); 500 if (hEvent != 0 && hEvent != INVALID_HANDLE_VALUE) 501 CloseHandle(hEvent); 502 } 503 504 // cancel single key 505 @Override implCancelKey(WatchKey obj)506 void implCancelKey(WatchKey obj) { 507 WindowsWatchKey key = (WindowsWatchKey)obj; 508 if (key.isValid()) { 509 fk2key.remove(key.fileKey()); 510 ck2key.remove(key.completionKey()); 511 key.invalidate(); 512 } 513 } 514 515 // close watch service 516 @Override implCloseAll()517 void implCloseAll() { 518 // cancel all keys 519 ck2key.values().forEach(WindowsWatchKey::invalidate); 520 521 fk2key.clear(); 522 ck2key.clear(); 523 524 // close I/O completion port 525 CloseHandle(port); 526 } 527 528 // Translate file change action into watch event translateActionToEvent(int action)529 private WatchEvent.Kind<?> translateActionToEvent(int action) { 530 switch (action) { 531 case FILE_ACTION_MODIFIED : 532 return StandardWatchEventKinds.ENTRY_MODIFY; 533 534 case FILE_ACTION_ADDED : 535 case FILE_ACTION_RENAMED_NEW_NAME : 536 return StandardWatchEventKinds.ENTRY_CREATE; 537 538 case FILE_ACTION_REMOVED : 539 case FILE_ACTION_RENAMED_OLD_NAME : 540 return StandardWatchEventKinds.ENTRY_DELETE; 541 542 default : 543 return null; // action not recognized 544 } 545 } 546 547 // process events (list of FILE_NOTIFY_INFORMATION structures) processEvents(WindowsWatchKey key, int size)548 private void processEvents(WindowsWatchKey key, int size) { 549 long address = key.buffer().address(); 550 551 int nextOffset; 552 do { 553 int action = UNSAFE.getInt(address + OFFSETOF_ACTION); 554 555 // map action to event 556 WatchEvent.Kind<?> kind = translateActionToEvent(action); 557 if (key.events().contains(kind)) { 558 // copy the name 559 int nameLengthInBytes = UNSAFE.getInt(address + OFFSETOF_FILENAMELENGTH); 560 if ((nameLengthInBytes % 2) != 0) { 561 throw new AssertionError("FileNameLength is not a multiple of 2"); 562 } 563 char[] nameAsArray = new char[nameLengthInBytes/2]; 564 UNSAFE.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray, 565 Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes); 566 567 // create FileName and queue event 568 WindowsPath name = WindowsPath 569 .createFromNormalizedPath(fs, new String(nameAsArray)); 570 key.signalEvent(kind, name); 571 } 572 573 // next event 574 nextOffset = UNSAFE.getInt(address + OFFSETOF_NEXTENTRYOFFSET); 575 address += (long)nextOffset; 576 } while (nextOffset != 0); 577 } 578 579 /** 580 * Poller main loop 581 */ 582 @Override run()583 public void run() { 584 for (;;) { 585 CompletionStatus info; 586 try { 587 info = GetQueuedCompletionStatus(port); 588 } catch (WindowsException x) { 589 // this should not happen 590 x.printStackTrace(); 591 return; 592 } 593 594 // wakeup 595 if (info.completionKey() == WAKEUP_COMPLETION_KEY) { 596 boolean shutdown = processRequests(); 597 if (shutdown) { 598 return; 599 } 600 continue; 601 } 602 603 // map completionKey to get WatchKey 604 WindowsWatchKey key = ck2key.get((int)info.completionKey()); 605 if (key == null) { 606 // We get here when a registration is changed. In that case 607 // the directory is closed which causes an event with the 608 // old completion key. 609 continue; 610 } 611 612 boolean criticalError = false; 613 int errorCode = info.error(); 614 int messageSize = info.bytesTransferred(); 615 if (errorCode == ERROR_NOTIFY_ENUM_DIR) { 616 // buffer overflow 617 key.signalEvent(StandardWatchEventKinds.OVERFLOW, null); 618 } else if (errorCode != 0 && errorCode != ERROR_MORE_DATA) { 619 // ReadDirectoryChangesW failed 620 criticalError = true; 621 } else { 622 // ERROR_MORE_DATA is a warning about incomplete 623 // data transfer over TCP/UDP stack. For the case 624 // [messageSize] is zero in the most of cases. 625 626 if (messageSize > 0) { 627 // process non-empty events. 628 processEvents(key, messageSize); 629 } else if (errorCode == 0) { 630 // insufficient buffer size 631 // not described, but can happen. 632 key.signalEvent(StandardWatchEventKinds.OVERFLOW, null); 633 } 634 635 // start read for next batch of changes 636 try { 637 ReadDirectoryChangesW(key.handle(), 638 key.buffer().address(), 639 CHANGES_BUFFER_SIZE, 640 key.watchSubtree(), 641 ALL_FILE_NOTIFY_EVENTS, 642 key.countAddress(), 643 key.overlappedAddress()); 644 } catch (WindowsException x) { 645 // no choice but to cancel key 646 criticalError = true; 647 key.setErrorStartingOverlapped(true); 648 } 649 } 650 if (criticalError) { 651 implCancelKey(key); 652 key.signal(); 653 } 654 } 655 } 656 } 657 } 658