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      */
WindowsWatchService(WindowsFileSystem fs)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
register(Path path, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)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
implClose()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 
WindowsWatchKey(Path dir, AbstractWatchService watcher, FileKey fileKey)120         WindowsWatchKey(Path dir,
121                         AbstractWatchService watcher,
122                         FileKey fileKey)
123         {
124             super(dir, watcher);
125             this.fileKey = fileKey;
126         }
127 
init(long handle, Set<? extends WatchEvent.Kind<?>> events, boolean watchSubtree, NativeBuffer buffer, long countAddress, long overlappedAddress, int completionKey)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 
handle()146         long handle() {
147             return handle;
148         }
149 
events()150         Set<? extends WatchEvent.Kind<?>> events() {
151             return events;
152         }
153 
setEvents(Set<? extends WatchEvent.Kind<?>> events)154         void setEvents(Set<? extends WatchEvent.Kind<?>> events) {
155             this.events = events;
156         }
157 
watchSubtree()158         boolean watchSubtree() {
159             return watchSubtree;
160         }
161 
buffer()162         NativeBuffer buffer() {
163             return buffer;
164         }
165 
countAddress()166         long countAddress() {
167             return countAddress;
168         }
169 
overlappedAddress()170         long overlappedAddress() {
171             return overlappedAddress;
172         }
173 
fileKey()174         FileKey fileKey() {
175             return fileKey;
176         }
177 
completionKey()178         int completionKey() {
179             return completionKey;
180         }
181 
setErrorStartingOverlapped(boolean value)182         void setErrorStartingOverlapped(boolean value) {
183             errorStartingOverlapped = value;
184         }
185 
isErrorStartingOverlapped()186         boolean isErrorStartingOverlapped() {
187             return errorStartingOverlapped;
188         }
189 
190         // Invalidate the key, assumes that resources have been released
invalidate()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
isValid()201         public boolean isValid() {
202             return handle != INVALID_HANDLE_VALUE;
203         }
204 
205         @Override
cancel()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 
FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow)220         FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow) {
221             this.volSerialNumber = volSerialNumber;
222             this.fileIndexHigh = fileIndexHigh;
223             this.fileIndexLow = fileIndexLow;
224         }
225 
226         @Override
hashCode()227         public int hashCode() {
228             return volSerialNumber ^ fileIndexHigh ^ fileIndexLow;
229         }
230 
231         @Override
equals(Object obj)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 final static 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 
Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port)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
wakeup()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
implRegister(Path obj, Set<? extends WatchEvent.Kind<?>> events, WatchEvent.Modifier... modifiers)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          */
releaseResources(WindowsWatchKey key)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().cleaner().clean();
482         }
483 
484         /**
485          * Creates an unnamed event and set it as the hEvent field
486          * in the given OVERLAPPED structure
487          */
createAndAttachEvent(long ov)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          */
closeAttachedEvent(long ov)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
implCancelKey(WatchKey obj)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
implCloseAll()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
translateActionToEvent(int action)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)
processEvents(WindowsWatchKey key, int size)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
run()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 incomplite
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 }
657