| /* | |
| * Copyright (c) 2008, 2023, 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 | |
| */ | |
| 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 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; | |
| return obj instanceof FileKey other | |
| && this.volSerialNumber == other.volSerialNumber | |
| && this.fileIndexHigh == other.fileIndexHigh | |
| && 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. | |
| */ | |
| 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 | |
| */ | |
| @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. | |
| */ | |
| 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 | |
| */ | |
| 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. | |
| */ | |
| 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 | |
| */ | |
| @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(); | |
| } | |
| } | |
| } | |
| } | |
| } |