blob: 5b9f98134b65f66fca74e630b9a643aaab9fe811 [file] [log] [blame]
/*
* Copyright (c) 2008, 2009, 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.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();
}
}