/*
 * Copyright (c) 2008, 2016, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.nio.fs;

import java.io.IOException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jdk.internal.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 static final int WAKEUP_COMPLETION_KEY = 0;

    // 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
/** * 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.
/** * Windows implementation of WatchKey. */
private static class WindowsWatchKey extends AbstractWatchKey { // file key (used to detect existing registrations) private final 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; // flag indicates that ReadDirectoryChangesW failed // and overlapped I/O operation wasn't started private boolean errorStartingOverlapped; WindowsWatchKey(Path dir, AbstractWatchService watcher, FileKey fileKey) { super(dir, 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; } void setErrorStartingOverlapped(boolean value) { errorStartingOverlapped = value; } boolean isErrorStartingOverlapped() { return errorStartingOverlapped; } // Invalidate the key, assumes that resources have been released void invalidate() { ((WindowsWatchService)watcher()).poller.releaseResources(this); handle = INVALID_HANDLE_VALUE; buffer = null; countAddress = 0; overlappedAddress = 0; errorStartingOverlapped = false; } @Override public boolean isValid() { return handle != INVALID_HANDLE_VALUE; } @Override public void cancel() { if (isValid()) { // delegate to poller ((WindowsWatchService)watcher()).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; return this.fileIndexLow == other.fileIndexLow; } } // 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.
/** * Background thread to service I/O completion port. */
private static class Poller extends AbstractPoller { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /* * typedef struct _OVERLAPPED { * ULONG_PTR Internal; * ULONG_PTR InternalHigh; * union { * struct { DWORD Offset; DWORD OffsetHigh; }; * PVOID Pointer; * }; * HANDLE hEvent; * } OVERLAPPED; */ private static final short SIZEOF_DWORD = 4; private static final short SIZEOF_OVERLAPPED = 32; // 20 on 32-bit private static final short OFFSETOF_HEVENT = (UNSAFE.addressSize() == 4) ? (short) 16 : 24; /* * 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) // Need to be less than 4*16384 = 65536. DWORD align. 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> ck2key; // maps file key to WatchKey private final Map<FileKey, WindowsWatchKey> fk2key; // unique completion key for each directory // native completion key capacity is 64 bits on Win64. private int lastCompletionKey; Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port) { this.fs = fs; this.watcher = watcher; this.port = port; this.ck2key = new HashMap<>(); this.fk2key = new HashMap<>(); this.lastCompletionKey = 0; } @Override void wakeup() throws IOException { try { PostQueuedCompletionStatus(port, WAKEUP_COMPLETION_KEY); } 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
/** * 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 (ExtendedOptions.FILE_TREE.matches(modifier)) { watchSubtree = true; } else { if (modifier == null) return new NullPointerException(); if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) && !ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) && !ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) { return new UnsupportedOperationException("Modifier not supported"); } } } // open directory long handle; 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; 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; } // Can overflow the int type capacity. // Skip WAKEUP_COMPLETION_KEY value. int completionKey = ++lastCompletionKey; if (completionKey == WAKEUP_COMPLETION_KEY) 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; // zero the overlapped structure UNSAFE.setMemory(overlappedAddress, SIZEOF_OVERLAPPED, (byte)0); // start async read of changes to directory try { createAndAttachEvent(overlappedAddress); ReadDirectoryChangesW(handle, bufferAddress, CHANGES_BUFFER_SIZE, watchSubtree, ALL_FILE_NOTIFY_EVENTS, countAddress, overlappedAddress); } catch (WindowsException x) { closeAttachedEvent(overlappedAddress); buffer.release(); return new IOException(x.getMessage()); } WindowsWatchKey watchKey; if (existing == null) { // not registered so create new watch key watchKey = new WindowsWatchKey(dir, 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 ck2key.remove(existing.completionKey()); releaseResources(existing); watchKey = existing.init(handle, events, watchSubtree, buffer, countAddress, overlappedAddress, completionKey); } // map completion map to watch key ck2key.put(completionKey, watchKey); registered = true; return watchKey; } finally { if (!registered) CloseHandle(handle); } }
Cancels the outstanding I/O operation on the directory associated with the given key and releases the associated resources.
/** * Cancels the outstanding I/O operation on the directory * associated with the given key and releases the associated * resources. */
private void releaseResources(WindowsWatchKey key) { if (!key.isErrorStartingOverlapped()) { try { CancelIo(key.handle()); GetOverlappedResult(key.handle(), key.overlappedAddress()); } catch (WindowsException expected) { // expected as I/O operation has been cancelled } } CloseHandle(key.handle()); closeAttachedEvent(key.overlappedAddress()); key.buffer().free(); }
Creates an unnamed event and set it as the hEvent field in the given OVERLAPPED structure
/** * Creates an unnamed event and set it as the hEvent field * in the given OVERLAPPED structure */
private void createAndAttachEvent(long ov) throws WindowsException { long hEvent = CreateEvent(false, false); UNSAFE.putAddress(ov + OFFSETOF_HEVENT, hEvent); }
Closes the event attached to the given OVERLAPPED structure. A no-op if there isn't an event attached.
/** * Closes the event attached to the given OVERLAPPED structure. A * no-op if there isn't an event attached. */
private void closeAttachedEvent(long ov) { long hEvent = UNSAFE.getAddress(ov + OFFSETOF_HEVENT); if (hEvent != 0 && hEvent != INVALID_HANDLE_VALUE) CloseHandle(hEvent); } // cancel single key @Override void implCancelKey(WatchKey obj) { WindowsWatchKey key = (WindowsWatchKey)obj; if (key.isValid()) { fk2key.remove(key.fileKey()); ck2key.remove(key.completionKey()); key.invalidate(); } } // close watch service @Override void implCloseAll() { // cancel all keys ck2key.values().forEach(WindowsWatchKey::invalidate); fk2key.clear(); ck2key.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 StandardWatchEventKinds.ENTRY_MODIFY; case FILE_ACTION_ADDED : case FILE_ACTION_RENAMED_NEW_NAME : return StandardWatchEventKinds.ENTRY_CREATE; case FILE_ACTION_REMOVED : case FILE_ACTION_RENAMED_OLD_NAME : return StandardWatchEventKinds.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 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
/** * Poller main loop */
@Override public void run() { for (;;) { CompletionStatus info; try { info = GetQueuedCompletionStatus(port); } catch (WindowsException x) { // this should not happen x.printStackTrace(); return; } // wakeup if (info.completionKey() == WAKEUP_COMPLETION_KEY) { boolean shutdown = processRequests(); if (shutdown) { return; } continue; } // map completionKey to get WatchKey WindowsWatchKey key = ck2key.get((int)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; } boolean criticalError = false; int errorCode = info.error(); int messageSize = info.bytesTransferred(); if (errorCode == ERROR_NOTIFY_ENUM_DIR) { // buffer overflow key.signalEvent(StandardWatchEventKinds.OVERFLOW, null); } else if (errorCode != 0 && errorCode != ERROR_MORE_DATA) { // ReadDirectoryChangesW failed criticalError = true; } else { // ERROR_MORE_DATA is a warning about incomplete // data transfer over TCP/UDP stack. For the case // [messageSize] is zero in the most of cases. if (messageSize > 0) { // process non-empty events. processEvents(key, messageSize); } else if (errorCode == 0) { // insufficient buffer size // not described, but can happen. key.signalEvent(StandardWatchEventKinds.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 criticalError = true; key.setErrorStartingOverlapped(true); } } if (criticalError) { implCancelKey(key); key.signal(); } } } } }