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 }