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