0N/A/*
2362N/A * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.nio.fs;
0N/A
0N/Aimport java.nio.file.*;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.util.*;
0N/Aimport java.io.IOException;
0N/Aimport sun.misc.Unsafe;
0N/A
0N/Aimport static sun.nio.fs.UnixNativeDispatcher.*;
0N/Aimport static sun.nio.fs.UnixConstants.*;
0N/A
0N/A/**
0N/A * Linux implementation of WatchService based on inotify.
0N/A *
0N/A * In summary a background thread polls inotify plus a socket used for the wakeup
0N/A * mechanism. Requests to add or remove a watch, or close the watch service,
0N/A * cause the thread to wakeup and process the request. Events are processed
0N/A * by the thread which causes it to signal/queue the corresponding watch keys.
0N/A */
0N/A
0N/Aclass LinuxWatchService
0N/A extends AbstractWatchService
0N/A{
0N/A private static final Unsafe unsafe = Unsafe.getUnsafe();
0N/A
0N/A // background thread to read change events
0N/A private final Poller poller;
0N/A
0N/A LinuxWatchService(UnixFileSystem fs) throws IOException {
0N/A // initialize inotify
0N/A int ifd = - 1;
0N/A try {
0N/A ifd = inotifyInit();
0N/A } catch (UnixException x) {
0N/A throw new IOException(x.errorString());
0N/A }
0N/A
0N/A // configure inotify to be non-blocking
0N/A // create socketpair used in the close mechanism
0N/A int sp[] = new int[2];
0N/A try {
0N/A configureBlocking(ifd, false);
0N/A socketpair(sp);
0N/A configureBlocking(sp[0], false);
0N/A } catch (UnixException x) {
0N/A UnixNativeDispatcher.close(ifd);
0N/A throw new IOException(x.errorString());
0N/A }
0N/A
0N/A this.poller = new Poller(fs, this, ifd, sp);
0N/A this.poller.start();
0N/A }
0N/A
0N/A @Override
0N/A WatchKey register(Path dir,
0N/A WatchEvent.Kind<?>[] events,
0N/A WatchEvent.Modifier... modifiers)
0N/A throws IOException
0N/A {
0N/A // delegate to poller
0N/A return poller.register(dir, events, modifiers);
0N/A }
0N/A
0N/A @Override
0N/A void implClose() throws IOException {
0N/A // delegate to poller
0N/A poller.close();
0N/A }
0N/A
0N/A /**
0N/A * WatchKey implementation
0N/A */
0N/A private static class LinuxWatchKey extends AbstractWatchKey {
0N/A // inotify descriptor
0N/A private final int ifd;
0N/A // watch descriptor
0N/A private volatile int wd;
0N/A
0N/A LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {
0N/A super(dir, watcher);
0N/A this.ifd = ifd;
0N/A this.wd = wd;
0N/A }
0N/A
0N/A int descriptor() {
0N/A return wd;
0N/A }
0N/A
0N/A void invalidate(boolean remove) {
0N/A if (remove) {
0N/A try {
0N/A inotifyRmWatch(ifd, wd);
0N/A } catch (UnixException x) {
0N/A // ignore
0N/A }
0N/A }
0N/A wd = -1;
0N/A }
0N/A
0N/A @Override
0N/A public boolean isValid() {
0N/A return (wd != -1);
0N/A }
0N/A
0N/A @Override
0N/A public void cancel() {
0N/A if (isValid()) {
0N/A // delegate to poller
0N/A ((LinuxWatchService)watcher()).poller.cancel(this);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Background thread to read from inotify
0N/A */
0N/A private static class Poller extends AbstractPoller {
0N/A /**
0N/A * struct inotify_event {
0N/A * int wd;
0N/A * uint32_t mask;
0N/A * uint32_t len;
0N/A * char name __flexarr; // present if len > 0
0N/A * } act_t;
0N/A */
0N/A private static final int SIZEOF_INOTIFY_EVENT = eventSize();
0N/A private static final int[] offsets = eventOffsets();
0N/A private static final int OFFSETOF_WD = offsets[0];
0N/A private static final int OFFSETOF_MASK = offsets[1];
0N/A private static final int OFFSETOF_LEN = offsets[3];
0N/A private static final int OFFSETOF_NAME = offsets[4];
0N/A
0N/A private static final int IN_MODIFY = 0x00000002;
0N/A private static final int IN_ATTRIB = 0x00000004;
0N/A private static final int IN_MOVED_FROM = 0x00000040;
0N/A private static final int IN_MOVED_TO = 0x00000080;
0N/A private static final int IN_CREATE = 0x00000100;
0N/A private static final int IN_DELETE = 0x00000200;
0N/A
0N/A private static final int IN_UNMOUNT = 0x00002000;
0N/A private static final int IN_Q_OVERFLOW = 0x00004000;
0N/A private static final int IN_IGNORED = 0x00008000;
0N/A
0N/A // sizeof buffer for when polling inotify
0N/A private static final int BUFFER_SIZE = 8192;
0N/A
0N/A private final UnixFileSystem fs;
0N/A private final LinuxWatchService watcher;
0N/A
0N/A // inotify file descriptor
0N/A private final int ifd;
0N/A // socketpair used to shutdown polling thread
0N/A private final int socketpair[];
0N/A // maps watch descriptor to Key
0N/A private final Map<Integer,LinuxWatchKey> wdToKey;
0N/A // address of read buffer
0N/A private final long address;
0N/A
0N/A Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
0N/A this.fs = fs;
0N/A this.watcher = watcher;
0N/A this.ifd = ifd;
0N/A this.socketpair = sp;
0N/A this.wdToKey = new HashMap<Integer,LinuxWatchKey>();
0N/A this.address = unsafe.allocateMemory(BUFFER_SIZE);
0N/A }
0N/A
0N/A @Override
0N/A void wakeup() throws IOException {
0N/A // write to socketpair to wakeup polling thread
0N/A try {
0N/A write(socketpair[1], address, 1);
0N/A } catch (UnixException x) {
0N/A throw new IOException(x.errorString());
0N/A }
0N/A }
0N/A
0N/A @Override
0N/A Object implRegister(Path obj,
0N/A Set<? extends WatchEvent.Kind<?>> events,
0N/A WatchEvent.Modifier... modifiers)
0N/A {
0N/A UnixPath dir = (UnixPath)obj;
0N/A
0N/A int mask = 0;
0N/A for (WatchEvent.Kind<?> event: events) {
0N/A if (event == StandardWatchEventKinds.ENTRY_CREATE) {
0N/A mask |= IN_CREATE | IN_MOVED_TO;
0N/A continue;
0N/A }
0N/A if (event == StandardWatchEventKinds.ENTRY_DELETE) {
0N/A mask |= IN_DELETE | IN_MOVED_FROM;
0N/A continue;
0N/A }
0N/A if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
0N/A mask |= IN_MODIFY | IN_ATTRIB;
0N/A continue;
0N/A }
0N/A }
0N/A
0N/A // no modifiers supported at this time
0N/A if (modifiers.length > 0) {
0N/A for (WatchEvent.Modifier modifier: modifiers) {
0N/A if (modifier == null)
0N/A return new NullPointerException();
0N/A if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
0N/A continue; // ignore
0N/A return new UnsupportedOperationException("Modifier not supported");
0N/A }
0N/A }
0N/A
0N/A // check file is directory
0N/A UnixFileAttributes attrs = null;
0N/A try {
0N/A attrs = UnixFileAttributes.get(dir, true);
0N/A } catch (UnixException x) {
0N/A return x.asIOException(dir);
0N/A }
0N/A if (!attrs.isDirectory()) {
0N/A return new NotDirectoryException(dir.getPathForExceptionMessage());
0N/A }
0N/A
0N/A // register with inotify (replaces existing mask if already registered)
0N/A int wd = -1;
0N/A try {
0N/A NativeBuffer buffer =
0N/A NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());
0N/A try {
0N/A wd = inotifyAddWatch(ifd, buffer.address(), mask);
0N/A } finally {
0N/A buffer.release();
0N/A }
0N/A } catch (UnixException x) {
0N/A if (x.errno() == ENOSPC) {
0N/A return new IOException("User limit of inotify watches reached");
0N/A }
0N/A return x.asIOException(dir);
0N/A }
0N/A
0N/A // ensure watch descriptor is in map
0N/A LinuxWatchKey key = wdToKey.get(wd);
0N/A if (key == null) {
0N/A key = new LinuxWatchKey(dir, watcher, ifd, wd);
0N/A wdToKey.put(wd, key);
0N/A }
0N/A return key;
0N/A }
0N/A
0N/A // cancel single key
0N/A @Override
0N/A void implCancelKey(WatchKey obj) {
0N/A LinuxWatchKey key = (LinuxWatchKey)obj;
0N/A if (key.isValid()) {
0N/A wdToKey.remove(key.descriptor());
0N/A key.invalidate(true);
0N/A }
0N/A }
0N/A
0N/A // close watch service
0N/A @Override
0N/A void implCloseAll() {
0N/A // invalidate all keys
0N/A for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
0N/A entry.getValue().invalidate(true);
0N/A }
0N/A wdToKey.clear();
0N/A
0N/A // free resources
0N/A unsafe.freeMemory(address);
0N/A UnixNativeDispatcher.close(socketpair[0]);
0N/A UnixNativeDispatcher.close(socketpair[1]);
0N/A UnixNativeDispatcher.close(ifd);
0N/A }
0N/A
0N/A /**
0N/A * Poller main loop
0N/A */
0N/A @Override
0N/A public void run() {
0N/A try {
0N/A for (;;) {
0N/A int nReady, bytesRead;
0N/A
0N/A // wait for close or inotify event
0N/A nReady = poll(ifd, socketpair[0]);
0N/A
0N/A // read from inotify
0N/A try {
0N/A bytesRead = read(ifd, address, BUFFER_SIZE);
0N/A } catch (UnixException x) {
0N/A if (x.errno() != EAGAIN)
0N/A throw x;
0N/A bytesRead = 0;
0N/A }
0N/A
0N/A // process any pending requests
0N/A if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
0N/A try {
0N/A read(socketpair[0], address, BUFFER_SIZE);
0N/A boolean shutdown = processRequests();
0N/A if (shutdown)
0N/A break;
0N/A } catch (UnixException x) {
0N/A if (x.errno() != UnixConstants.EAGAIN)
0N/A throw x;
0N/A }
0N/A }
0N/A
0N/A // iterate over buffer to decode events
0N/A int offset = 0;
0N/A while (offset < bytesRead) {
0N/A long event = address + offset;
0N/A int wd = unsafe.getInt(event + OFFSETOF_WD);
0N/A int mask = unsafe.getInt(event + OFFSETOF_MASK);
0N/A int len = unsafe.getInt(event + OFFSETOF_LEN);
0N/A
0N/A // file name
0N/A UnixPath name = null;
0N/A if (len > 0) {
0N/A int actual = len;
0N/A
0N/A // null-terminated and maybe additional null bytes to
0N/A // align the next event
0N/A while (actual > 0) {
0N/A long last = event + OFFSETOF_NAME + actual - 1;
0N/A if (unsafe.getByte(last) != 0)
0N/A break;
0N/A actual--;
0N/A }
0N/A if (actual > 0) {
0N/A byte[] buf = new byte[actual];
0N/A unsafe.copyMemory(null, event + OFFSETOF_NAME,
0N/A buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
0N/A name = new UnixPath(fs, buf);
}
}
// process event
processEvent(wd, mask, name);
offset += (SIZEOF_INOTIFY_EVENT + len);
}
}
} catch (UnixException x) {
x.printStackTrace();
}
}
/**
* map inotify event to WatchEvent.Kind
*/
private WatchEvent.Kind<?> maskToEventKind(int mask) {
if ((mask & IN_MODIFY) > 0)
return StandardWatchEventKinds.ENTRY_MODIFY;
if ((mask & IN_ATTRIB) > 0)
return StandardWatchEventKinds.ENTRY_MODIFY;
if ((mask & IN_CREATE) > 0)
return StandardWatchEventKinds.ENTRY_CREATE;
if ((mask & IN_MOVED_TO) > 0)
return StandardWatchEventKinds.ENTRY_CREATE;
if ((mask & IN_DELETE) > 0)
return StandardWatchEventKinds.ENTRY_DELETE;
if ((mask & IN_MOVED_FROM) > 0)
return StandardWatchEventKinds.ENTRY_DELETE;
return null;
}
/**
* Process event from inotify
*/
private void processEvent(int wd, int mask, final UnixPath name) {
// overflow - signal all keys
if ((mask & IN_Q_OVERFLOW) > 0) {
for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
entry.getValue()
.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
}
return;
}
// lookup wd to get key
LinuxWatchKey key = wdToKey.get(wd);
if (key == null)
return; // should not happen
// file deleted
if ((mask & IN_IGNORED) > 0) {
wdToKey.remove(wd);
key.invalidate(false);
key.signal();
return;
}
// event for directory itself
if (name == null)
return;
// map to event and queue to key
WatchEvent.Kind<?> kind = maskToEventKind(mask);
if (kind != null) {
key.signalEvent(kind, name);
}
}
}
// -- native methods --
// sizeof inotify_event
private static native int eventSize();
// offsets of inotify_event
private static native int[] eventOffsets();
private static native int inotifyInit() throws UnixException;
private static native int inotifyAddWatch(int fd, long pathAddress, int mask)
throws UnixException;
private static native void inotifyRmWatch(int fd, int wd)
throws UnixException;
private static native void configureBlocking(int fd, boolean blocking)
throws UnixException;
private static native void socketpair(int[] sv) throws UnixException;
private static native int poll(int fd1, int fd2) throws UnixException;
static {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
System.loadLibrary("nio");
return null;
}});
}
}