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