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