Path: blob/master/src/java.base/windows/classes/sun/nio/fs/WindowsWatchService.java
41139 views
/*1* Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.nio.fs;2627import java.io.IOException;28import java.nio.file.NotDirectoryException;29import java.nio.file.Path;30import java.nio.file.StandardWatchEventKinds;31import java.nio.file.WatchEvent;32import java.nio.file.WatchKey;33import java.util.HashMap;34import java.util.Map;35import java.util.Set;3637import jdk.internal.misc.Unsafe;3839import static sun.nio.fs.WindowsNativeDispatcher.*;40import static sun.nio.fs.WindowsConstants.*;4142/*43* Win32 implementation of WatchService based on ReadDirectoryChangesW.44*/4546class WindowsWatchService47extends AbstractWatchService48{49private static final int WAKEUP_COMPLETION_KEY = 0;5051// background thread to service I/O completion port52private final Poller poller;5354/**55* Creates an I/O completion port and a daemon thread to service it56*/57WindowsWatchService(WindowsFileSystem fs) throws IOException {58// create I/O completion port59long port = 0L;60try {61port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0);62} catch (WindowsException x) {63throw new IOException(x.getMessage());64}6566this.poller = new Poller(fs, this, port);67this.poller.start();68}6970@Override71WatchKey register(Path path,72WatchEvent.Kind<?>[] events,73WatchEvent.Modifier... modifiers)74throws IOException75{76// delegate to poller77return poller.register(path, events, modifiers);78}7980@Override81void implClose() throws IOException {82// delegate to poller83poller.close();84}8586/**87* Windows implementation of WatchKey.88*/89private static class WindowsWatchKey extends AbstractWatchKey {90// file key (used to detect existing registrations)91private final FileKey fileKey;9293// handle to directory94private volatile long handle = INVALID_HANDLE_VALUE;9596// interest events97private Set<? extends WatchEvent.Kind<?>> events;9899// subtree100private boolean watchSubtree;101102// buffer for change events103private NativeBuffer buffer;104105// pointer to bytes returned (in buffer)106private long countAddress;107108// pointer to overlapped structure (in buffer)109private long overlappedAddress;110111// completion key (used to map I/O completion to WatchKey)112private int completionKey;113114// flag indicates that ReadDirectoryChangesW failed115// and overlapped I/O operation wasn't started116private boolean errorStartingOverlapped;117118WindowsWatchKey(Path dir,119AbstractWatchService watcher,120FileKey fileKey)121{122super(dir, watcher);123this.fileKey = fileKey;124}125126WindowsWatchKey init(long handle,127Set<? extends WatchEvent.Kind<?>> events,128boolean watchSubtree,129NativeBuffer buffer,130long countAddress,131long overlappedAddress,132int completionKey)133{134this.handle = handle;135this.events = events;136this.watchSubtree = watchSubtree;137this.buffer = buffer;138this.countAddress = countAddress;139this.overlappedAddress = overlappedAddress;140this.completionKey = completionKey;141return this;142}143144long handle() {145return handle;146}147148Set<? extends WatchEvent.Kind<?>> events() {149return events;150}151152void setEvents(Set<? extends WatchEvent.Kind<?>> events) {153this.events = events;154}155156boolean watchSubtree() {157return watchSubtree;158}159160NativeBuffer buffer() {161return buffer;162}163164long countAddress() {165return countAddress;166}167168long overlappedAddress() {169return overlappedAddress;170}171172FileKey fileKey() {173return fileKey;174}175176int completionKey() {177return completionKey;178}179180void setErrorStartingOverlapped(boolean value) {181errorStartingOverlapped = value;182}183184boolean isErrorStartingOverlapped() {185return errorStartingOverlapped;186}187188// Invalidate the key, assumes that resources have been released189void invalidate() {190((WindowsWatchService)watcher()).poller.releaseResources(this);191handle = INVALID_HANDLE_VALUE;192buffer = null;193countAddress = 0;194overlappedAddress = 0;195errorStartingOverlapped = false;196}197198@Override199public boolean isValid() {200return handle != INVALID_HANDLE_VALUE;201}202203@Override204public void cancel() {205if (isValid()) {206// delegate to poller207((WindowsWatchService)watcher()).poller.cancel(this);208}209}210}211212// file key to unique identify (open) directory213private static class FileKey {214private final int volSerialNumber;215private final int fileIndexHigh;216private final int fileIndexLow;217218FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow) {219this.volSerialNumber = volSerialNumber;220this.fileIndexHigh = fileIndexHigh;221this.fileIndexLow = fileIndexLow;222}223224@Override225public int hashCode() {226return volSerialNumber ^ fileIndexHigh ^ fileIndexLow;227}228229@Override230public boolean equals(Object obj) {231if (obj == this)232return true;233if (!(obj instanceof FileKey))234return false;235FileKey other = (FileKey)obj;236if (this.volSerialNumber != other.volSerialNumber) return false;237if (this.fileIndexHigh != other.fileIndexHigh) return false;238return this.fileIndexLow == other.fileIndexLow;239}240}241242// all change events243private static final int ALL_FILE_NOTIFY_EVENTS =244FILE_NOTIFY_CHANGE_FILE_NAME |245FILE_NOTIFY_CHANGE_DIR_NAME |246FILE_NOTIFY_CHANGE_ATTRIBUTES |247FILE_NOTIFY_CHANGE_SIZE |248FILE_NOTIFY_CHANGE_LAST_WRITE |249FILE_NOTIFY_CHANGE_CREATION |250FILE_NOTIFY_CHANGE_SECURITY;251252/**253* Background thread to service I/O completion port.254*/255private static class Poller extends AbstractPoller {256private static final Unsafe UNSAFE = Unsafe.getUnsafe();257258/*259* typedef struct _OVERLAPPED {260* ULONG_PTR Internal;261* ULONG_PTR InternalHigh;262* union {263* struct { DWORD Offset; DWORD OffsetHigh; };264* PVOID Pointer;265* };266* HANDLE hEvent;267* } OVERLAPPED;268*/269private static final short SIZEOF_DWORD = 4;270private static final short SIZEOF_OVERLAPPED = 32; // 20 on 32-bit271private static final short OFFSETOF_HEVENT =272(UNSAFE.addressSize() == 4) ? (short) 16 : 24;273274275/*276* typedef struct _FILE_NOTIFY_INFORMATION {277* DWORD NextEntryOffset;278* DWORD Action;279* DWORD FileNameLength;280* WCHAR FileName[1];281* } FileNameLength;282*/283private static final short OFFSETOF_NEXTENTRYOFFSET = 0;284private static final short OFFSETOF_ACTION = 4;285private static final short OFFSETOF_FILENAMELENGTH = 8;286private static final short OFFSETOF_FILENAME = 12;287288// size of per-directory buffer for events (FIXME - make this configurable)289// Need to be less than 4*16384 = 65536. DWORD align.290private static final int CHANGES_BUFFER_SIZE = 16 * 1024;291292private final WindowsFileSystem fs;293private final WindowsWatchService watcher;294private final long port;295296// maps completion key to WatchKey297private final Map<Integer, WindowsWatchKey> ck2key;298299// maps file key to WatchKey300private final Map<FileKey, WindowsWatchKey> fk2key;301302// unique completion key for each directory303// native completion key capacity is 64 bits on Win64.304private int lastCompletionKey;305306Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port) {307this.fs = fs;308this.watcher = watcher;309this.port = port;310this.ck2key = new HashMap<>();311this.fk2key = new HashMap<>();312this.lastCompletionKey = 0;313}314315@Override316void wakeup() throws IOException {317try {318PostQueuedCompletionStatus(port, WAKEUP_COMPLETION_KEY);319} catch (WindowsException x) {320throw new IOException(x.getMessage());321}322}323324/**325* Register a directory for changes as follows:326*327* 1. Open directory328* 2. Read its attributes (and check it really is a directory)329* 3. Assign completion key and associated handle with completion port330* 4. Call ReadDirectoryChangesW to start (async) read of changes331* 5. Create or return existing key representing registration332*/333@Override334Object implRegister(Path obj,335Set<? extends WatchEvent.Kind<?>> events,336WatchEvent.Modifier... modifiers)337{338WindowsPath dir = (WindowsPath)obj;339boolean watchSubtree = false;340341// FILE_TREE modifier allowed342for (WatchEvent.Modifier modifier: modifiers) {343if (ExtendedOptions.FILE_TREE.matches(modifier)) {344watchSubtree = true;345} else {346if (modifier == null)347return new NullPointerException();348if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) &&349!ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) &&350!ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {351return new UnsupportedOperationException("Modifier not supported");352}353}354}355356// open directory357long handle;358try {359handle = CreateFile(dir.getPathForWin32Calls(),360FILE_LIST_DIRECTORY,361(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),362OPEN_EXISTING,363FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED);364} catch (WindowsException x) {365return x.asIOException(dir);366}367368boolean registered = false;369try {370// read attributes and check file is a directory371WindowsFileAttributes attrs;372try {373attrs = WindowsFileAttributes.readAttributes(handle);374} catch (WindowsException x) {375return x.asIOException(dir);376}377if (!attrs.isDirectory()) {378return new NotDirectoryException(dir.getPathForExceptionMessage());379}380381// check if this directory is already registered382FileKey fk = new FileKey(attrs.volSerialNumber(),383attrs.fileIndexHigh(),384attrs.fileIndexLow());385WindowsWatchKey existing = fk2key.get(fk);386387// if already registered and we're not changing the subtree388// modifier then simply update the event and return the key.389if (existing != null && watchSubtree == existing.watchSubtree()) {390existing.setEvents(events);391return existing;392}393394// Can overflow the int type capacity.395// Skip WAKEUP_COMPLETION_KEY value.396int completionKey = ++lastCompletionKey;397if (completionKey == WAKEUP_COMPLETION_KEY)398completionKey = ++lastCompletionKey;399400// associate handle with completion port401try {402CreateIoCompletionPort(handle, port, completionKey);403} catch (WindowsException x) {404return new IOException(x.getMessage());405}406407// allocate memory for events, including space for other structures408// needed to do overlapped I/O409int size = CHANGES_BUFFER_SIZE + SIZEOF_DWORD + SIZEOF_OVERLAPPED;410NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);411412long bufferAddress = buffer.address();413long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED;414long countAddress = overlappedAddress - SIZEOF_DWORD;415416// zero the overlapped structure417UNSAFE.setMemory(overlappedAddress, SIZEOF_OVERLAPPED, (byte)0);418419// start async read of changes to directory420try {421createAndAttachEvent(overlappedAddress);422423ReadDirectoryChangesW(handle,424bufferAddress,425CHANGES_BUFFER_SIZE,426watchSubtree,427ALL_FILE_NOTIFY_EVENTS,428countAddress,429overlappedAddress);430} catch (WindowsException x) {431closeAttachedEvent(overlappedAddress);432buffer.release();433return new IOException(x.getMessage());434}435436WindowsWatchKey watchKey;437if (existing == null) {438// not registered so create new watch key439watchKey = new WindowsWatchKey(dir, watcher, fk)440.init(handle, events, watchSubtree, buffer, countAddress,441overlappedAddress, completionKey);442// map file key to watch key443fk2key.put(fk, watchKey);444} else {445// directory already registered so need to:446// 1. remove mapping from old completion key to existing watch key447// 2. release existing key's resources (handle/buffer)448// 3. re-initialize key with new handle/buffer449ck2key.remove(existing.completionKey());450releaseResources(existing);451watchKey = existing.init(handle, events, watchSubtree, buffer,452countAddress, overlappedAddress, completionKey);453}454// map completion map to watch key455ck2key.put(completionKey, watchKey);456457registered = true;458return watchKey;459460} finally {461if (!registered) CloseHandle(handle);462}463}464465/**466* Cancels the outstanding I/O operation on the directory467* associated with the given key and releases the associated468* resources.469*/470private void releaseResources(WindowsWatchKey key) {471if (!key.isErrorStartingOverlapped()) {472try {473CancelIo(key.handle());474GetOverlappedResult(key.handle(), key.overlappedAddress());475} catch (WindowsException expected) {476// expected as I/O operation has been cancelled477}478}479CloseHandle(key.handle());480closeAttachedEvent(key.overlappedAddress());481key.buffer().free();482}483484/**485* Creates an unnamed event and set it as the hEvent field486* in the given OVERLAPPED structure487*/488private void createAndAttachEvent(long ov) throws WindowsException {489long hEvent = CreateEvent(false, false);490UNSAFE.putAddress(ov + OFFSETOF_HEVENT, hEvent);491}492493/**494* Closes the event attached to the given OVERLAPPED structure. A495* no-op if there isn't an event attached.496*/497private void closeAttachedEvent(long ov) {498long hEvent = UNSAFE.getAddress(ov + OFFSETOF_HEVENT);499if (hEvent != 0 && hEvent != INVALID_HANDLE_VALUE)500CloseHandle(hEvent);501}502503// cancel single key504@Override505void implCancelKey(WatchKey obj) {506WindowsWatchKey key = (WindowsWatchKey)obj;507if (key.isValid()) {508fk2key.remove(key.fileKey());509ck2key.remove(key.completionKey());510key.invalidate();511}512}513514// close watch service515@Override516void implCloseAll() {517// cancel all keys518ck2key.values().forEach(WindowsWatchKey::invalidate);519520fk2key.clear();521ck2key.clear();522523// close I/O completion port524CloseHandle(port);525}526527// Translate file change action into watch event528private WatchEvent.Kind<?> translateActionToEvent(int action) {529switch (action) {530case FILE_ACTION_MODIFIED :531return StandardWatchEventKinds.ENTRY_MODIFY;532533case FILE_ACTION_ADDED :534case FILE_ACTION_RENAMED_NEW_NAME :535return StandardWatchEventKinds.ENTRY_CREATE;536537case FILE_ACTION_REMOVED :538case FILE_ACTION_RENAMED_OLD_NAME :539return StandardWatchEventKinds.ENTRY_DELETE;540541default :542return null; // action not recognized543}544}545546// process events (list of FILE_NOTIFY_INFORMATION structures)547private void processEvents(WindowsWatchKey key, int size) {548long address = key.buffer().address();549550int nextOffset;551do {552int action = UNSAFE.getInt(address + OFFSETOF_ACTION);553554// map action to event555WatchEvent.Kind<?> kind = translateActionToEvent(action);556if (key.events().contains(kind)) {557// copy the name558int nameLengthInBytes = UNSAFE.getInt(address + OFFSETOF_FILENAMELENGTH);559if ((nameLengthInBytes % 2) != 0) {560throw new AssertionError("FileNameLength is not a multiple of 2");561}562char[] nameAsArray = new char[nameLengthInBytes/2];563UNSAFE.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray,564Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);565566// create FileName and queue event567WindowsPath name = WindowsPath568.createFromNormalizedPath(fs, new String(nameAsArray));569key.signalEvent(kind, name);570}571572// next event573nextOffset = UNSAFE.getInt(address + OFFSETOF_NEXTENTRYOFFSET);574address += (long)nextOffset;575} while (nextOffset != 0);576}577578/**579* Poller main loop580*/581@Override582public void run() {583for (;;) {584CompletionStatus info;585try {586info = GetQueuedCompletionStatus(port);587} catch (WindowsException x) {588// this should not happen589x.printStackTrace();590return;591}592593// wakeup594if (info.completionKey() == WAKEUP_COMPLETION_KEY) {595boolean shutdown = processRequests();596if (shutdown) {597return;598}599continue;600}601602// map completionKey to get WatchKey603WindowsWatchKey key = ck2key.get((int)info.completionKey());604if (key == null) {605// We get here when a registration is changed. In that case606// the directory is closed which causes an event with the607// old completion key.608continue;609}610611boolean criticalError = false;612int errorCode = info.error();613int messageSize = info.bytesTransferred();614if (errorCode == ERROR_NOTIFY_ENUM_DIR) {615// buffer overflow616key.signalEvent(StandardWatchEventKinds.OVERFLOW, null);617} else if (errorCode != 0 && errorCode != ERROR_MORE_DATA) {618// ReadDirectoryChangesW failed619criticalError = true;620} else {621// ERROR_MORE_DATA is a warning about incomplete622// data transfer over TCP/UDP stack. For the case623// [messageSize] is zero in the most of cases.624625if (messageSize > 0) {626// process non-empty events.627processEvents(key, messageSize);628} else if (errorCode == 0) {629// insufficient buffer size630// not described, but can happen.631key.signalEvent(StandardWatchEventKinds.OVERFLOW, null);632}633634// start read for next batch of changes635try {636ReadDirectoryChangesW(key.handle(),637key.buffer().address(),638CHANGES_BUFFER_SIZE,639key.watchSubtree(),640ALL_FILE_NOTIFY_EVENTS,641key.countAddress(),642key.overlappedAddress());643} catch (WindowsException x) {644// no choice but to cancel key645criticalError = true;646key.setErrorStartingOverlapped(true);647}648}649if (criticalError) {650implCancelKey(key);651key.signal();652}653}654}655}656}657658659