--- old/src/java.base/solaris/classes/sun/nio/fs/SolarisWatchService.java 2020-05-20 18:10:23.522045718 -0700 +++ /dev/null 2020-03-09 18:57:19.455001459 -0700 @@ -1,821 +0,0 @@ -/* - * Copyright (c) 2008, 2019, 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.nio.file.*; -import java.util.*; -import java.io.IOException; -import jdk.internal.misc.Unsafe; - -import static sun.nio.fs.UnixConstants.*; - -/** - * Solaris implementation of WatchService based on file events notification - * facility. - */ - -class SolarisWatchService - extends AbstractWatchService -{ - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static int addressSize = unsafe.addressSize(); - - private static int dependsArch(int value32, int value64) { - return (addressSize == 4) ? value32 : value64; - } - - /* - * typedef struct port_event { - * int portev_events; - * ushort_t portev_source; - * ushort_t portev_pad; - * uintptr_t portev_object; - * void *portev_user; - * } port_event_t; - */ - private static final int SIZEOF_PORT_EVENT = dependsArch(16, 24); - private static final int OFFSETOF_EVENTS = 0; - private static final int OFFSETOF_SOURCE = 4; - private static final int OFFSETOF_OBJECT = 8; - - /* - * typedef struct file_obj { - * timestruc_t fo_atime; - * timestruc_t fo_mtime; - * timestruc_t fo_ctime; - * uintptr_t fo_pad[3]; - * char *fo_name; - * } file_obj_t; - */ - private static final int SIZEOF_FILEOBJ = dependsArch(40, 80); - private static final int OFFSET_FO_NAME = dependsArch(36, 72); - - // port sources - private static final short PORT_SOURCE_USER = 3; - private static final short PORT_SOURCE_FILE = 7; - - // user-watchable events - private static final int FILE_MODIFIED = 0x00000002; - private static final int FILE_ATTRIB = 0x00000004; - private static final int FILE_NOFOLLOW = 0x10000000; - - // exception events - private static final int FILE_DELETE = 0x00000010; - private static final int FILE_RENAME_TO = 0x00000020; - private static final int FILE_RENAME_FROM = 0x00000040; - private static final int UNMOUNTED = 0x20000000; - private static final int MOUNTEDOVER = 0x40000000; - - // background thread to read change events - private final Poller poller; - - SolarisWatchService(UnixFileSystem fs) throws IOException { - int port = -1; - try { - port = portCreate(); - } catch (UnixException x) { - throw new IOException(x.errorString()); - } - - this.poller = new Poller(fs, this, port); - this.poller.start(); - } - - @Override - WatchKey register(Path dir, - WatchEvent.Kind[] events, - WatchEvent.Modifier... modifiers) - throws IOException - { - // delegate to poller - return poller.register(dir, events, modifiers); - } - - @Override - void implClose() throws IOException { - // delegate to poller - poller.close(); - } - - /** - * WatchKey implementation - */ - private class SolarisWatchKey extends AbstractWatchKey - implements DirectoryNode - { - private final UnixFileKey fileKey; - - // pointer to native file_obj object - private final long object; - - // events (may be changed). set to null when watch key is invalid - private volatile Set> events; - - // map of entries in directory; created lazily; accessed only by - // poller thread. - private Map children = new HashMap<>(); - - SolarisWatchKey(SolarisWatchService watcher, - UnixPath dir, - UnixFileKey fileKey, - long object, - Set> events) - { - super(dir, watcher); - this.fileKey = fileKey; - this.object = object; - this.events = events; - } - - UnixPath getDirectory() { - return (UnixPath)watchable(); - } - - UnixFileKey getFileKey() { - return fileKey; - } - - @Override - public long object() { - return object; - } - - void invalidate() { - events = null; - } - - Set> events() { - return events; - } - - void setEvents(Set> events) { - this.events = events; - } - - Map children() { - return children; - } - - @Override - public boolean isValid() { - return events != null; - } - - @Override - public void cancel() { - if (isValid()) { - // delegate to poller - poller.cancel(this); - } - } - - @Override - public void addChild(Path name, EntryNode node) { - children.put(name, node); - } - - @Override - public void removeChild(Path name) { - children.remove(name); - } - - @Override - public EntryNode getChild(Path name) { - return children.get(name); - } - } - - /** - * Background thread to read from port - */ - private class Poller extends AbstractPoller { - - // maximum number of events to read per call to port_getn - private static final int MAX_EVENT_COUNT = 128; - - // events that map to ENTRY_DELETE - private static final int FILE_REMOVED = - (FILE_DELETE|FILE_RENAME_TO|FILE_RENAME_FROM); - - // events that tell us not to re-associate the object - private static final int FILE_EXCEPTION = - (FILE_REMOVED|UNMOUNTED|MOUNTEDOVER); - - // address of event buffers (used to receive events with port_getn) - private final long bufferAddress; - - private final SolarisWatchService watcher; - - // the I/O port - private final int port; - - // maps file key (dev/inode) to WatchKey - private final Map fileKey2WatchKey; - - // maps file_obj object to Node - private final Map object2Node; - - /** - * Create a new instance - */ - Poller(UnixFileSystem fs, SolarisWatchService watcher, int port) { - this.watcher = watcher; - this.port = port; - this.bufferAddress = - unsafe.allocateMemory(SIZEOF_PORT_EVENT * MAX_EVENT_COUNT); - this.fileKey2WatchKey = new HashMap(); - this.object2Node = new HashMap(); - } - - @Override - void wakeup() throws IOException { - // write to port to wakeup polling thread - try { - portSend(port, 0); - } catch (UnixException x) { - throw new IOException(x.errorString()); - } - } - - @Override - Object implRegister(Path obj, - Set> events, - WatchEvent.Modifier... modifiers) - { - // no modifiers supported at this time - if (modifiers.length > 0) { - for (WatchEvent.Modifier modifier: modifiers) { - 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"); - } - } - } - - UnixPath dir = (UnixPath)obj; - - // check file is directory - UnixFileAttributes attrs = null; - try { - attrs = UnixFileAttributes.get(dir, true); - } catch (UnixException x) { - return x.asIOException(dir); - } - if (!attrs.isDirectory()) { - return new NotDirectoryException(dir.getPathForExceptionMessage()); - } - - // if already registered then update the events and return existing key - UnixFileKey fileKey = attrs.fileKey(); - SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey); - if (watchKey != null) { - try { - updateEvents(watchKey, events); - } catch (UnixException x) { - return x.asIOException(dir); - } - return watchKey; - } - - // register directory - long object = 0L; - try { - object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB)); - } catch (UnixException x) { - return x.asIOException(dir); - } - - // create watch key and insert it into maps - watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events); - object2Node.put(object, watchKey); - fileKey2WatchKey.put(fileKey, watchKey); - - // register all entries in directory - registerChildren(dir, watchKey, false, false); - - return watchKey; - } - - // release resources for single entry - void releaseChild(EntryNode node) { - long object = node.object(); - if (object != 0L) { - object2Node.remove(object); - releaseObject(object, true); - node.setObject(0L); - } - } - - // release resources for entries in directory - void releaseChildren(SolarisWatchKey key) { - for (EntryNode node: key.children().values()) { - releaseChild(node); - } - } - - // cancel single key - @Override - void implCancelKey(WatchKey obj) { - SolarisWatchKey key = (SolarisWatchKey)obj; - if (key.isValid()) { - fileKey2WatchKey.remove(key.getFileKey()); - - // release resources for entries - releaseChildren(key); - - // release resources for directory - long object = key.object(); - object2Node.remove(object); - releaseObject(object, true); - - // and finally invalidate the key - key.invalidate(); - } - } - - // close watch service - @Override - void implCloseAll() { - // release all native resources - for (Long object: object2Node.keySet()) { - releaseObject(object, true); - } - - // invalidate all keys - for (Map.Entry entry: fileKey2WatchKey.entrySet()) { - entry.getValue().invalidate(); - } - - // clean-up - object2Node.clear(); - fileKey2WatchKey.clear(); - - // free global resources - unsafe.freeMemory(bufferAddress); - UnixNativeDispatcher.close(port); - } - - /** - * Poller main loop. Blocks on port_getn waiting for events and then - * processes them. - */ - @Override - public void run() { - try { - for (;;) { - int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT); - assert n > 0; - - long address = bufferAddress; - for (int i=0; iportev_source - short source = unsafe.getShort(address + OFFSETOF_SOURCE); - // pe->portev_object - long object = unsafe.getAddress(address + OFFSETOF_OBJECT); - // pe->portev_events - int events = unsafe.getInt(address + OFFSETOF_EVENTS); - - // user event is trigger to process pending requests - if (source != PORT_SOURCE_FILE) { - if (source == PORT_SOURCE_USER) { - // process any pending requests - boolean shutdown = processRequests(); - if (shutdown) - return true; - } - return false; - } - - // lookup object to get Node - Node node = object2Node.get(object); - if (node == null) { - // should not happen - return false; - } - - // As a workaround for 6642290 and 6636438/6636412 we don't use - // FILE_EXCEPTION events to tell use not to register the file. - // boolean reregister = (events & FILE_EXCEPTION) == 0; - boolean reregister = true; - - // If node is EntryNode then event relates to entry in directory - // If node is a SolarisWatchKey (DirectoryNode) then event relates - // to a watched directory. - boolean isDirectory = (node instanceof SolarisWatchKey); - if (isDirectory) { - processDirectoryEvents((SolarisWatchKey)node, events); - } else { - boolean ignore = processEntryEvents((EntryNode)node, events); - if (ignore) - reregister = false; - } - - // need to re-associate to get further events - if (reregister) { - try { - events = FILE_MODIFIED | FILE_ATTRIB; - if (!isDirectory) events |= FILE_NOFOLLOW; - portAssociate(port, - PORT_SOURCE_FILE, - object, - events); - } catch (UnixException x) { - // unable to re-register - reregister = false; - } - } - - // object is not re-registered so release resources. If - // object is a watched directory then signal key - if (!reregister) { - // release resources - object2Node.remove(object); - releaseObject(object, false); - - // if watch key then signal it - if (isDirectory) { - SolarisWatchKey key = (SolarisWatchKey)node; - fileKey2WatchKey.remove( key.getFileKey() ); - key.invalidate(); - key.signal(); - } else { - // if entry then remove it from parent - EntryNode entry = (EntryNode)node; - SolarisWatchKey key = (SolarisWatchKey)entry.parent(); - key.removeChild(entry.name()); - } - } - - return false; - } - - /** - * Process directory events. If directory is modified then re-scan - * directory to register any new entries - */ - void processDirectoryEvents(SolarisWatchKey key, int mask) { - if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) { - registerChildren(key.getDirectory(), key, - key.events().contains(StandardWatchEventKinds.ENTRY_CREATE), - key.events().contains(StandardWatchEventKinds.ENTRY_DELETE)); - } - } - - /** - * Process events for entries in registered directories. Returns {@code - * true} if events are ignored because the watch key has been cancelled. - */ - boolean processEntryEvents(EntryNode node, int mask) { - SolarisWatchKey key = (SolarisWatchKey)node.parent(); - Set> events = key.events(); - if (events == null) { - // key has been cancelled so ignore event - return true; - } - - // entry modified - if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) && - events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) - { - key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name()); - } - - - return false; - } - - /** - * Registers all entries in the given directory - * - * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters - * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued - * when new entries are found. When initially registering a directory - * they will always be false. When re-scanning a directory then it - * depends on if the events are enabled or not. - */ - void registerChildren(UnixPath dir, - SolarisWatchKey parent, - boolean sendCreateEvents, - boolean sendDeleteEvents) - { - boolean isModifyEnabled = - parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ; - - // reset visited flag on entries so that we can detect file deletes - for (EntryNode node: parent.children().values()) { - node.setVisited(false); - } - - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { - for (Path entry: stream) { - Path name = entry.getFileName(); - - // skip entry if already registered - EntryNode node = parent.getChild(name); - if (node != null) { - node.setVisited(true); - continue; - } - - // new entry found - - long object = 0L; - int errno = 0; - boolean addNode = false; - - // if ENTRY_MODIFY enabled then we register the entry for events - if (isModifyEnabled) { - try { - UnixPath path = (UnixPath)entry; - int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); - object = registerImpl(path, events); - addNode = true; - } catch (UnixException x) { - errno = x.errno(); - } - } else { - addNode = true; - } - - if (addNode) { - // create node - node = new EntryNode(object, (UnixPath)entry.getFileName(), parent); - node.setVisited(true); - // tell the parent about it - parent.addChild(entry.getFileName(), node); - if (object != 0L) - object2Node.put(object, node); - } - - // send ENTRY_CREATE event for the new file - // send ENTRY_DELETE event for files that were deleted immediately - boolean deleted = (errno == ENOENT); - if (sendCreateEvents && (addNode || deleted)) - parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name); - if (sendDeleteEvents && deleted) - parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name); - - } - } catch (DirectoryIteratorException | IOException x) { - // queue OVERFLOW event so that user knows to re-scan directory - parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null); - return; - } - - // clean-up and send ENTRY_DELETE events for any entries that were - // not found - Iterator> iterator = - parent.children().entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - EntryNode node = entry.getValue(); - if (!node.isVisited()) { - long object = node.object(); - if (object != 0L) { - object2Node.remove(object); - releaseObject(object, true); - } - if (sendDeleteEvents) - parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name()); - iterator.remove(); - } - } - } - - /** - * Update watch key's events. If ENTRY_MODIFY changes to be enabled - * then register each file in the directory; If ENTRY_MODIFY changed to - * be disabled then unregister each file. - */ - void updateEvents(SolarisWatchKey key, Set> events) - throws UnixException - { - - // update events, remembering if ENTRY_MODIFY was previously - // enabled or disabled. - boolean oldModifyEnabled = key.events() - .contains(StandardWatchEventKinds.ENTRY_MODIFY); - key.setEvents(events); - - // check if ENTRY_MODIFY has changed - boolean newModifyEnabled = events - .contains(StandardWatchEventKinds.ENTRY_MODIFY); - if (newModifyEnabled != oldModifyEnabled) { - UnixException ex = null; - for (EntryNode node: key.children().values()) { - if (newModifyEnabled) { - // register - UnixPath path = key.getDirectory().resolve(node.name()); - int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); - try { - long object = registerImpl(path, ev); - object2Node.put(object, node); - node.setObject(object); - } catch (UnixException x) { - // if file has been deleted then it will be detected - // as a FILE_MODIFIED event on the directory - if (x.errno() != ENOENT) { - ex = x; - break; - } - } - } else { - // unregister - releaseChild(node); - } - } - - // an error occurred - if (ex != null) { - releaseChildren(key); - throw ex; - } - } - } - - /** - * Calls port_associate to register the given path. - * Returns pointer to fileobj structure that is allocated for - * the registration. - */ - long registerImpl(UnixPath dir, int events) - throws UnixException - { - // allocate memory for the path (file_obj->fo_name field) - byte[] path = dir.getByteArrayForSysCalls(); - int len = path.length; - long name = unsafe.allocateMemory(len+1); - unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, - name, (long)len); - unsafe.putByte(name + len, (byte)0); - - // allocate memory for filedatanode structure - this is the object - // to port_associate - long object = unsafe.allocateMemory(SIZEOF_FILEOBJ); - unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0); - unsafe.putAddress(object + OFFSET_FO_NAME, name); - - // associate the object with the port - try { - portAssociate(port, - PORT_SOURCE_FILE, - object, - events); - } catch (UnixException x) { - // debugging - if (x.errno() == EAGAIN) { - System.err.println("The maximum number of objects associated "+ - "with the port has been reached"); - } - - unsafe.freeMemory(name); - unsafe.freeMemory(object); - throw x; - } - return object; - } - - /** - * Frees all resources for an file_obj object; optionally remove - * association from port - */ - void releaseObject(long object, boolean dissociate) { - // remove association - if (dissociate) { - try { - portDissociate(port, PORT_SOURCE_FILE, object); - } catch (UnixException x) { - // ignore - } - } - - // free native memory - long name = unsafe.getAddress(object + OFFSET_FO_NAME); - unsafe.freeMemory(name); - unsafe.freeMemory(object); - } - } - - /** - * A node with native (file_obj) resources - */ - private static interface Node { - long object(); - } - - /** - * A directory node with a map of the entries in the directory - */ - private static interface DirectoryNode extends Node { - void addChild(Path name, EntryNode node); - void removeChild(Path name); - EntryNode getChild(Path name); - } - - /** - * An implementation of a node that is an entry in a directory. - */ - private static class EntryNode implements Node { - private long object; - private final UnixPath name; - private final DirectoryNode parent; - private boolean visited; - - EntryNode(long object, UnixPath name, DirectoryNode parent) { - this.object = object; - this.name = name; - this.parent = parent; - } - - @Override - public long object() { - return object; - } - - void setObject(long ptr) { - this.object = ptr; - } - - UnixPath name() { - return name; - } - - DirectoryNode parent() { - return parent; - } - - boolean isVisited() { - return visited; - } - - void setVisited(boolean v) { - this.visited = v; - } - } - - // -- native methods -- - - private static native void init(); - - private static native int portCreate() throws UnixException; - - private static native void portAssociate(int port, int source, long object, int events) - throws UnixException; - - private static native void portDissociate(int port, int source, long object) - throws UnixException; - - private static native void portSend(int port, int events) - throws UnixException; - - private static native int portGetn(int port, long address, int max) - throws UnixException; - - static { - jdk.internal.loader.BootLoader.loadLibrary("nio"); - init(); - } -}