jdk/src/windows/classes/sun/nio/fs/WindowsWatchService.java
changeset 2057 3acf8e5e2ca0
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/windows/classes/sun/nio/fs/WindowsWatchService.java	Sun Feb 15 12:25:54 2009 +0000
@@ -0,0 +1,582 @@
+/*
+ * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.nio.fs;
+
+import java.nio.file.*;
+import java.io.IOException;
+import java.util.*;
+import com.sun.nio.file.ExtendedWatchEventModifier;
+import sun.misc.Unsafe;
+
+import static sun.nio.fs.WindowsNativeDispatcher.*;
+import static sun.nio.fs.WindowsConstants.*;
+
+/*
+ * Win32 implementation of WatchService based on ReadDirectoryChangesW.
+ */
+
+class WindowsWatchService
+    extends AbstractWatchService
+{
+    private final Unsafe unsafe = Unsafe.getUnsafe();
+
+    // background thread to service I/O completion port
+    private final Poller poller;
+
+    /**
+     * Creates an I/O completion port and a daemon thread to service it
+     */
+    WindowsWatchService(WindowsFileSystem fs) throws IOException {
+        // create I/O completion port
+        long port = 0L;
+        try {
+            port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0);
+        } catch (WindowsException x) {
+            throw new IOException(x.getMessage());
+        }
+
+        this.poller = new Poller(fs, this, port);
+        this.poller.start();
+    }
+
+    @Override
+    WatchKey register(Path path,
+                      WatchEvent.Kind<?>[] events,
+                      WatchEvent.Modifier... modifiers)
+         throws IOException
+    {
+        // delegate to poller
+        return poller.register(path, events, modifiers);
+    }
+
+    @Override
+    void implClose() throws IOException {
+        // delegate to poller
+        poller.close();
+    }
+
+    /**
+     * Windows implementation of WatchKey.
+     */
+    private class WindowsWatchKey extends AbstractWatchKey {
+        // file key (used to detect existing registrations)
+        private FileKey fileKey;
+
+        // handle to directory
+        private volatile long handle = INVALID_HANDLE_VALUE;
+
+        // interest events
+        private Set<? extends WatchEvent.Kind<?>> events;
+
+        // subtree
+        private boolean watchSubtree;
+
+        // buffer for change events
+        private NativeBuffer buffer;
+
+        // pointer to bytes returned (in buffer)
+        private long countAddress;
+
+        // pointer to overlapped structure (in buffer)
+        private long overlappedAddress;
+
+        // completion key (used to map I/O completion to WatchKey)
+        private int completionKey;
+
+        WindowsWatchKey(AbstractWatchService watcher, FileKey fileKey) {
+            super(watcher);
+            this.fileKey = fileKey;
+        }
+
+        WindowsWatchKey init(long handle,
+                             Set<? extends WatchEvent.Kind<?>> events,
+                             boolean watchSubtree,
+                             NativeBuffer buffer,
+                             long countAddress,
+                             long overlappedAddress,
+                             int completionKey)
+        {
+            this.handle = handle;
+            this.events = events;
+            this.watchSubtree = watchSubtree;
+            this.buffer = buffer;
+            this.countAddress = countAddress;
+            this.overlappedAddress = overlappedAddress;
+            this.completionKey = completionKey;
+            return this;
+        }
+
+        long handle() {
+            return handle;
+        }
+
+        Set<? extends WatchEvent.Kind<?>> events() {
+            return events;
+        }
+
+        void setEvents(Set<? extends WatchEvent.Kind<?>> events) {
+            this.events = events;
+        }
+
+        boolean watchSubtree() {
+            return watchSubtree;
+        }
+
+        NativeBuffer buffer() {
+            return buffer;
+        }
+
+        long countAddress() {
+            return countAddress;
+        }
+
+        long overlappedAddress() {
+            return overlappedAddress;
+        }
+
+        FileKey fileKey() {
+            return fileKey;
+        }
+
+        int completionKey() {
+            return completionKey;
+        }
+
+        // close directory and release buffer
+        void releaseResources() {
+            CloseHandle(handle);
+            buffer.cleaner().clean();
+        }
+
+        // Invalidate key by closing directory and releasing buffer
+        void invalidate() {
+            releaseResources();
+            handle = INVALID_HANDLE_VALUE;
+            buffer = null;
+            countAddress = 0;
+            overlappedAddress = 0;
+        }
+
+        @Override
+        public boolean isValid() {
+            return handle != INVALID_HANDLE_VALUE;
+        }
+
+        @Override
+        public void cancel() {
+            if (isValid()) {
+                // delegate to poller
+                poller.cancel(this);
+            }
+        }
+    }
+
+    // file key to unique identify (open) directory
+    private static class FileKey {
+        private final int volSerialNumber;
+        private final int fileIndexHigh;
+        private final int fileIndexLow;
+
+        FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow) {
+            this.volSerialNumber = volSerialNumber;
+            this.fileIndexHigh = fileIndexHigh;
+            this.fileIndexLow = fileIndexLow;
+        }
+
+        @Override
+        public int hashCode() {
+            return volSerialNumber ^ fileIndexHigh ^ fileIndexLow;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof FileKey))
+                return false;
+            FileKey other = (FileKey)obj;
+            if (this.volSerialNumber != other.volSerialNumber) return false;
+            if (this.fileIndexHigh != other.fileIndexHigh) return false;
+            if (this.fileIndexLow != other.fileIndexLow) return false;
+            return true;
+        }
+    }
+
+    // all change events
+    private static final int ALL_FILE_NOTIFY_EVENTS =
+        FILE_NOTIFY_CHANGE_FILE_NAME |
+        FILE_NOTIFY_CHANGE_DIR_NAME |
+        FILE_NOTIFY_CHANGE_ATTRIBUTES  |
+        FILE_NOTIFY_CHANGE_SIZE |
+        FILE_NOTIFY_CHANGE_LAST_WRITE |
+        FILE_NOTIFY_CHANGE_CREATION |
+        FILE_NOTIFY_CHANGE_SECURITY;
+
+    /**
+     * Background thread to service I/O completion port.
+     */
+    private class Poller extends AbstractPoller {
+        /*
+         * typedef struct _OVERLAPPED {
+         *     DWORD  Internal;
+         *     DWORD  InternalHigh;
+         *     DWORD  Offset;
+         *     DWORD  OffsetHigh;
+         *     HANDLE hEvent;
+         * } OVERLAPPED;
+         */
+        private static final short SIZEOF_DWORD         = 4;
+        private static final short SIZEOF_OVERLAPPED    = 32; // 20 on 32-bit
+
+        /*
+         * typedef struct _FILE_NOTIFY_INFORMATION {
+         *     DWORD NextEntryOffset;
+         *     DWORD Action;
+         *     DWORD FileNameLength;
+         *     WCHAR FileName[1];
+         * } FileNameLength;
+         */
+        private static final short OFFSETOF_NEXTENTRYOFFSET = 0;
+        private static final short OFFSETOF_ACTION          = 4;
+        private static final short OFFSETOF_FILENAMELENGTH  = 8;
+        private static final short OFFSETOF_FILENAME        = 12;
+
+        // size of per-directory buffer for events (FIXME - make this configurable)
+        private static final int CHANGES_BUFFER_SIZE    = 16 * 1024;
+
+        private final WindowsFileSystem fs;
+        private final WindowsWatchService watcher;
+        private final long port;
+
+        // maps completion key to WatchKey
+        private final Map<Integer,WindowsWatchKey> int2key;
+
+        // maps file key to WatchKey
+        private final Map<FileKey,WindowsWatchKey> fk2key;
+
+        // unique completion key for each directory
+        private int lastCompletionKey;
+
+        Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port) {
+            this.fs = fs;
+            this.watcher = watcher;
+            this.port = port;
+            this.int2key = new HashMap<Integer,WindowsWatchKey>();
+            this.fk2key = new HashMap<FileKey,WindowsWatchKey>();
+            this.lastCompletionKey = 0;
+        }
+
+        @Override
+        void wakeup() throws IOException {
+            try {
+                PostQueuedCompletionStatus(port, 0);
+            } catch (WindowsException x) {
+                throw new IOException(x.getMessage());
+            }
+        }
+
+        /**
+         * Register a directory for changes as follows:
+         *
+         * 1. Open directory
+         * 2. Read its attributes (and check it really is a directory)
+         * 3. Assign completion key and associated handle with completion port
+         * 4. Call ReadDirectoryChangesW to start (async) read of changes
+         * 5. Create or return existing key representing registration
+         */
+        @Override
+        Object implRegister(Path obj,
+                            Set<? extends WatchEvent.Kind<?>> events,
+                            WatchEvent.Modifier... modifiers)
+        {
+            WindowsPath dir = (WindowsPath)obj;
+            boolean watchSubtree = false;
+
+            // FILE_TREE modifier allowed
+            for (WatchEvent.Modifier modifier: modifiers) {
+                if (modifier == ExtendedWatchEventModifier.FILE_TREE) {
+                    watchSubtree = true;
+                    continue;
+                } else {
+                    if (modifier == null)
+                        return new NullPointerException();
+                    if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
+                        continue; // ignore
+                    return new UnsupportedOperationException("Modifier not supported");
+                }
+            }
+
+            // open directory
+            long handle = -1L;
+            try {
+                handle = CreateFile(dir.getPathForWin32Calls(),
+                                    FILE_LIST_DIRECTORY,
+                                    (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
+                                    OPEN_EXISTING,
+                                    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED);
+            } catch (WindowsException x) {
+                return x.asIOException(dir);
+            }
+
+            boolean registered = false;
+            try {
+                // read attributes and check file is a directory
+                WindowsFileAttributes attrs = null;
+                try {
+                    attrs = WindowsFileAttributes.readAttributes(handle);
+                } catch (WindowsException x) {
+                    return x.asIOException(dir);
+                }
+                if (!attrs.isDirectory()) {
+                    return new NotDirectoryException(dir.getPathForExceptionMessage());
+                }
+
+                // check if this directory is already registered
+                FileKey fk = new FileKey(attrs.volSerialNumber(),
+                                         attrs.fileIndexHigh(),
+                                         attrs.fileIndexLow());
+                WindowsWatchKey existing = fk2key.get(fk);
+
+                // if already registered and we're not changing the subtree
+                // modifier then simply update the event and return the key.
+                if (existing != null && watchSubtree == existing.watchSubtree()) {
+                    existing.setEvents(events);
+                    return existing;
+                }
+
+                // unique completion key (skip 0)
+                int completionKey = ++lastCompletionKey;
+                if (completionKey == 0)
+                    completionKey = ++lastCompletionKey;
+
+                // associate handle with completion port
+                try {
+                    CreateIoCompletionPort(handle, port, completionKey);
+                } catch (WindowsException x) {
+                    return new IOException(x.getMessage());
+                }
+
+                // allocate memory for events, including space for other structures
+                // needed to do overlapped I/O
+                int size = CHANGES_BUFFER_SIZE + SIZEOF_DWORD + SIZEOF_OVERLAPPED;
+                NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
+
+                long bufferAddress = buffer.address();
+                long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED;
+                long countAddress = overlappedAddress - SIZEOF_DWORD;
+
+                // start async read of changes to directory
+                try {
+                    ReadDirectoryChangesW(handle,
+                                          bufferAddress,
+                                          CHANGES_BUFFER_SIZE,
+                                          watchSubtree,
+                                          ALL_FILE_NOTIFY_EVENTS,
+                                          countAddress,
+                                          overlappedAddress);
+                } catch (WindowsException x) {
+                    buffer.release();
+                    return new IOException(x.getMessage());
+                }
+
+                WindowsWatchKey watchKey;
+                if (existing == null) {
+                    // not registered so create new watch key
+                    watchKey = new WindowsWatchKey(watcher, fk)
+                        .init(handle, events, watchSubtree, buffer, countAddress,
+                              overlappedAddress, completionKey);
+                    // map file key to watch key
+                    fk2key.put(fk, watchKey);
+                } else {
+                    // directory already registered so need to:
+                    // 1. remove mapping from old completion key to existing watch key
+                    // 2. release existing key's resources (handle/buffer)
+                    // 3. re-initialize key with new handle/buffer
+                    int2key.remove(existing.completionKey());
+                    existing.releaseResources();
+                    watchKey = existing.init(handle, events, watchSubtree, buffer,
+                        countAddress, overlappedAddress, completionKey);
+                }
+                // map completion map to watch key
+                int2key.put(completionKey, watchKey);
+
+                registered = true;
+                return watchKey;
+
+            } finally {
+                if (!registered) CloseHandle(handle);
+            }
+        }
+
+        // cancel single key
+        @Override
+        void implCancelKey(WatchKey obj) {
+            WindowsWatchKey key = (WindowsWatchKey)obj;
+            if (key.isValid()) {
+                fk2key.remove(key.fileKey());
+                int2key.remove(key.completionKey());
+                key.invalidate();
+            }
+        }
+
+        // close watch service
+        @Override
+        void implCloseAll() {
+            // cancel all keys
+            for (Map.Entry<Integer,WindowsWatchKey> entry: int2key.entrySet()) {
+                entry.getValue().invalidate();
+            }
+            fk2key.clear();
+            int2key.clear();
+
+            // close I/O completion port
+            CloseHandle(port);
+        }
+
+        // Translate file change action into watch event
+        private WatchEvent.Kind<?> translateActionToEvent(int action)
+        {
+            switch (action) {
+                case FILE_ACTION_MODIFIED :
+                    return StandardWatchEventKind.ENTRY_MODIFY;
+
+                case FILE_ACTION_ADDED :
+                case FILE_ACTION_RENAMED_NEW_NAME :
+                    return StandardWatchEventKind.ENTRY_CREATE;
+
+                case FILE_ACTION_REMOVED :
+                case FILE_ACTION_RENAMED_OLD_NAME :
+                    return StandardWatchEventKind.ENTRY_DELETE;
+
+                default :
+                    return null;  // action not recognized
+            }
+        }
+
+        // process events (list of FILE_NOTIFY_INFORMATION structures)
+        private void processEvents(WindowsWatchKey key, int size) {
+            long address = key.buffer().address();
+
+            int nextOffset;
+            do {
+                int action = unsafe.getInt(address + OFFSETOF_ACTION);
+
+                // map action to event
+                WatchEvent.Kind<?> kind = translateActionToEvent(action);
+                if (key.events().contains(kind)) {
+                    // copy the name
+                    int nameLengthInBytes = unsafe.getInt(address + OFFSETOF_FILENAMELENGTH);
+                    if ((nameLengthInBytes % 2) != 0) {
+                        throw new AssertionError("FileNameLength.FileNameLength is not a multiple of 2");
+                    }
+                    char[] nameAsArray = new char[nameLengthInBytes/2];
+                    unsafe.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray,
+                        Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
+
+                    // create FileName and queue event
+                    WindowsPath name = WindowsPath
+                        .createFromNormalizedPath(fs, new String(nameAsArray));
+                    key.signalEvent(kind, name);
+                }
+
+                // next event
+                nextOffset = unsafe.getInt(address + OFFSETOF_NEXTENTRYOFFSET);
+                address += (long)nextOffset;
+            } while (nextOffset != 0);
+        }
+
+        /**
+         * Poller main loop
+         */
+        @Override
+        public void run() {
+            for (;;) {
+                CompletionStatus info = null;
+                try {
+                    info = GetQueuedCompletionStatus(port);
+                } catch (WindowsException x) {
+                    // this should not happen
+                    x.printStackTrace();
+                    return;
+                }
+
+                // wakeup
+                if (info.completionKey() == 0) {
+                    boolean shutdown = processRequests();
+                    if (shutdown) {
+                        return;
+                    }
+                    continue;
+                }
+
+                // map completionKey to get WatchKey
+                WindowsWatchKey key = int2key.get(info.completionKey());
+                if (key == null) {
+                    // We get here when a registration is changed. In that case
+                    // the directory is closed which causes an event with the
+                    // old completion key.
+                    continue;
+                }
+
+                // ReadDirectoryChangesW failed
+                if (info.error() != 0) {
+                    // buffer overflow
+                    if (info.error() == ERROR_NOTIFY_ENUM_DIR) {
+                        key.signalEvent(StandardWatchEventKind.OVERFLOW, null);
+                    } else {
+                        // other error so cancel key
+                        implCancelKey(key);
+                        key.signal();
+                    }
+                    continue;
+                }
+
+                // process the events
+                if (info.bytesTransferred() > 0) {
+                    processEvents(key, info.bytesTransferred());
+                } else {
+                    // insufficient buffer size
+                    key.signalEvent(StandardWatchEventKind.OVERFLOW, null);
+                }
+
+                // start read for next batch of changes
+                try {
+                    ReadDirectoryChangesW(key.handle(),
+                                          key.buffer().address(),
+                                          CHANGES_BUFFER_SIZE,
+                                          key.watchSubtree(),
+                                          ALL_FILE_NOTIFY_EVENTS,
+                                          key.countAddress(),
+                                          key.overlappedAddress());
+                } catch (WindowsException x) {
+                    // no choice but to cancel key
+                    implCancelKey(key);
+                    key.signal();
+                }
+            }
+        }
+    }
+}