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