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