--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java Sun Feb 15 12:25:54 2009 +0000
@@ -0,0 +1,770 @@
+/*
+ * 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.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.io.IOException;
+import sun.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 UnixPath dir;
+ 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<? extends WatchEvent.Kind<?>> events;
+
+ // map of entries in directory; created lazily; accessed only by
+ // poller thread.
+ private Map<Path,EntryNode> children;
+
+ SolarisWatchKey(SolarisWatchService watcher,
+ UnixPath dir,
+ UnixFileKey fileKey,
+ long object,
+ Set<? extends WatchEvent.Kind<?>> events)
+ {
+ super(watcher);
+ this.dir = dir;
+ this.fileKey = fileKey;
+ this.object = object;
+ this.events = events;
+ }
+
+ UnixPath getFileRef() {
+ return dir;
+ }
+
+ UnixFileKey getFileKey() {
+ return fileKey;
+ }
+
+ @Override
+ public long object() {
+ return object;
+ }
+
+ void invalidate() {
+ events = null;
+ }
+
+ Set<? extends WatchEvent.Kind<?>> events() {
+ return events;
+ }
+
+ void setEvents(Set<? extends WatchEvent.Kind<?>> events) {
+ this.events = events;
+ }
+
+ @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) {
+ if (children == null)
+ children = new HashMap<Path,EntryNode>();
+ children.put(name, node);
+ }
+
+ @Override
+ public void removeChild(Path name) {
+ children.remove(name);
+ }
+
+ @Override
+ public EntryNode getChild(Path name) {
+ if (children != null)
+ return children.get(name);
+ return null;
+ }
+ }
+
+ /**
+ * 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<UnixFileKey,SolarisWatchKey> fileKey2WatchKey;
+
+ // maps file_obj object to Node
+ private final Map<Long,Node> 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<UnixFileKey,SolarisWatchKey>();
+ this.object2Node = new HashMap<Long,Node>();
+ }
+
+ @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<? extends WatchEvent.Kind<?>> 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 (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
+ continue; // ignore
+ 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.getPathForExecptionMessage());
+ }
+
+ // return existing watch key after updating events if already
+ // registered
+ UnixFileKey fileKey = attrs.fileKey();
+ SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey);
+ if (watchKey != null) {
+ updateEvents(watchKey, events);
+ 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);
+
+ return watchKey;
+ }
+
+ // cancel single key
+ @Override
+ void implCancelKey(WatchKey obj) {
+ SolarisWatchKey key = (SolarisWatchKey)obj;
+ if (key.isValid()) {
+ fileKey2WatchKey.remove(key.getFileKey());
+
+ // release resources for entries in directory
+ if (key.children != null) {
+ for (Map.Entry<Path,EntryNode> entry: key.children.entrySet()) {
+ EntryNode node = entry.getValue();
+ long object = node.object();
+ object2Node.remove(object);
+ releaseObject(object, true);
+ }
+ }
+
+ // 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<UnixFileKey,SolarisWatchKey> 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; i<n; i++) {
+ boolean shutdown = processEvent(address);
+ if (shutdown)
+ return;
+ address += SIZEOF_PORT_EVENT;
+ }
+ }
+ } catch (UnixException x) {
+ x.printStackTrace();
+ }
+ }
+
+ /**
+ * Process a single port_event
+ *
+ * Returns true if poller thread is requested to shutdown.
+ */
+ boolean processEvent(long address) {
+ // pe->portev_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.getFileRef(), key,
+ key.events().contains(StandardWatchEventKind.ENTRY_CREATE));
+ }
+ }
+
+ /**
+ * 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<? extends WatchEvent.Kind<?>> 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(StandardWatchEventKind.ENTRY_MODIFY))
+ {
+ key.signalEvent(StandardWatchEventKind.ENTRY_MODIFY, node.name());
+ }
+
+ // entry removed
+ if (((mask & (FILE_REMOVED)) != 0) &&
+ events.contains(StandardWatchEventKind.ENTRY_DELETE))
+ {
+ // Due to 6636438/6636412 we may get a remove event for cases
+ // where a rmdir/unlink/rename is attempted but fails. Until
+ // this issue is resolved we re-lstat the file to check if it
+ // exists. If it exists then we ignore the event. To keep the
+ // workaround simple we don't check the st_ino so it isn't
+ // effective when the file is replaced.
+ boolean removed = true;
+ try {
+ UnixFileAttributes
+ .get(key.getFileRef().resolve(node.name()), false);
+ removed = false;
+ } catch (UnixException x) { }
+
+ if (removed)
+ key.signalEvent(StandardWatchEventKind.ENTRY_DELETE, node.name());
+ }
+ return false;
+ }
+
+ /**
+ * Registers all entries in the given directory
+ *
+ * The {@code sendEvents} parameter indicates if ENTRY_CREATE events
+ * should be queued when new entries are found. When initially
+ * registering a directory then will always be false. When re-scanning
+ * a directory then it depends on if the event is enabled or not.
+ */
+ void registerChildren(UnixPath dir,
+ SolarisWatchKey parent,
+ boolean sendEvents)
+ {
+ // if the ENTRY_MODIFY event is not enabled then we don't need
+ // modification events for entries in the directory
+ int events = FILE_NOFOLLOW;
+ if (parent.events().contains(StandardWatchEventKind.ENTRY_MODIFY))
+ events |= (FILE_MODIFIED | FILE_ATTRIB);
+
+ DirectoryStream<Path> stream = null;
+ try {
+ stream = dir.newDirectoryStream();
+ } catch (IOException x) {
+ // nothing we can do
+ return;
+ }
+ try {
+ for (Path entry: stream) {
+ Path name = entry.getName();
+
+ // skip entry if already registered
+ if (parent.getChild(name) != null)
+ continue;
+
+ // send ENTRY_CREATE if enabled
+ if (sendEvents) {
+ parent.signalEvent(StandardWatchEventKind.ENTRY_CREATE, name);
+ }
+
+ // register it
+ long object = 0L;
+ try {
+ object = registerImpl((UnixPath)entry, events);
+ } catch (UnixException x) {
+ // can't register so ignore for now.
+ continue;
+ }
+
+ // create node
+ EntryNode node = new EntryNode(object, entry.getName(), parent);
+ // tell the parent about it
+ parent.addChild(entry.getName(), node);
+ object2Node.put(object, node);
+ }
+ } catch (ConcurrentModificationException x) {
+ // error during iteration which we ignore for now
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException x) { }
+ }
+ }
+
+ /**
+ * Update watch key's events. Where the ENTRY_MODIFY changes then we
+ * need to update the events of registered children.
+ */
+ void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events) {
+ // update events, rembering if ENTRY_MODIFY was previously
+ // enabled or disabled.
+ boolean wasModifyEnabled = key.events()
+ .contains(StandardWatchEventKind.ENTRY_MODIFY);
+ key.setEvents(events);
+
+ // check if ENTRY_MODIFY has changed
+ boolean isModifyEnabled = events
+ .contains(StandardWatchEventKind.ENTRY_MODIFY);
+ if (wasModifyEnabled == isModifyEnabled) {
+ return;
+ }
+
+ // if changed then update events of children
+ if (key.children != null) {
+ int ev = FILE_NOFOLLOW;
+ if (isModifyEnabled)
+ ev |= (FILE_MODIFIED | FILE_ATTRIB);
+
+ for (Map.Entry<Path,EntryNode> entry: key.children.entrySet()) {
+ long object = entry.getValue().object();
+ try {
+ portAssociate(port,
+ PORT_SOURCE_FILE,
+ object,
+ ev);
+ } catch (UnixException x) {
+ // nothing we can do.
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 final long object;
+ private final Path name;
+ private final DirectoryNode parent;
+
+ EntryNode(long object, Path name, DirectoryNode parent) {
+ this.object = object;
+ this.name = name;
+ this.parent = parent;
+ }
+
+ @Override
+ public long object() {
+ return object;
+ }
+
+ Path name() {
+ return name;
+ }
+
+ DirectoryNode parent() {
+ return parent;
+ }
+ }
+
+ // -- 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 {
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ System.loadLibrary("nio");
+ return null;
+ }});
+ init();
+ }
+}