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