--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/linux/classes/sun/nio/fs/LinuxWatchService.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2008, 2016, 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 jdk.internal.misc.Unsafe;
+
+import static sun.nio.fs.UnixNativeDispatcher.*;
+import static sun.nio.fs.UnixConstants.*;
+
+/**
+ * Linux implementation of WatchService based on inotify.
+ *
+ * In summary a background thread polls inotify plus a socket used for the wakeup
+ * mechanism. Requests to add or remove a watch, or close the watch service,
+ * cause the thread to wakeup and process the request. Events are processed
+ * by the thread which causes it to signal/queue the corresponding watch keys.
+ */
+
+class LinuxWatchService
+ extends AbstractWatchService
+{
+ private static final Unsafe unsafe = Unsafe.getUnsafe();
+
+ // background thread to read change events
+ private final Poller poller;
+
+ LinuxWatchService(UnixFileSystem fs) throws IOException {
+ // initialize inotify
+ int ifd = - 1;
+ try {
+ ifd = inotifyInit();
+ } catch (UnixException x) {
+ String msg = (x.errno() == EMFILE) ?
+ "User limit of inotify instances reached or too many open files" :
+ x.errorString();
+ throw new IOException(msg);
+ }
+
+ // configure inotify to be non-blocking
+ // create socketpair used in the close mechanism
+ int sp[] = new int[2];
+ try {
+ configureBlocking(ifd, false);
+ socketpair(sp);
+ configureBlocking(sp[0], false);
+ } catch (UnixException x) {
+ UnixNativeDispatcher.close(ifd);
+ throw new IOException(x.errorString());
+ }
+
+ this.poller = new Poller(fs, this, ifd, sp);
+ 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 static class LinuxWatchKey extends AbstractWatchKey {
+ // inotify descriptor
+ private final int ifd;
+ // watch descriptor
+ private volatile int wd;
+
+ LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {
+ super(dir, watcher);
+ this.ifd = ifd;
+ this.wd = wd;
+ }
+
+ int descriptor() {
+ return wd;
+ }
+
+ void invalidate(boolean remove) {
+ if (remove) {
+ try {
+ inotifyRmWatch(ifd, wd);
+ } catch (UnixException x) {
+ // ignore
+ }
+ }
+ wd = -1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return (wd != -1);
+ }
+
+ @Override
+ public void cancel() {
+ if (isValid()) {
+ // delegate to poller
+ ((LinuxWatchService)watcher()).poller.cancel(this);
+ }
+ }
+ }
+
+ /**
+ * Background thread to read from inotify
+ */
+ private static class Poller extends AbstractPoller {
+ /**
+ * struct inotify_event {
+ * int wd;
+ * uint32_t mask;
+ * uint32_t len;
+ * char name __flexarr; // present if len > 0
+ * } act_t;
+ */
+ private static final int SIZEOF_INOTIFY_EVENT = eventSize();
+ private static final int[] offsets = eventOffsets();
+ private static final int OFFSETOF_WD = offsets[0];
+ private static final int OFFSETOF_MASK = offsets[1];
+ private static final int OFFSETOF_LEN = offsets[3];
+ private static final int OFFSETOF_NAME = offsets[4];
+
+ private static final int IN_MODIFY = 0x00000002;
+ private static final int IN_ATTRIB = 0x00000004;
+ private static final int IN_MOVED_FROM = 0x00000040;
+ private static final int IN_MOVED_TO = 0x00000080;
+ private static final int IN_CREATE = 0x00000100;
+ private static final int IN_DELETE = 0x00000200;
+
+ private static final int IN_UNMOUNT = 0x00002000;
+ private static final int IN_Q_OVERFLOW = 0x00004000;
+ private static final int IN_IGNORED = 0x00008000;
+
+ // sizeof buffer for when polling inotify
+ private static final int BUFFER_SIZE = 8192;
+
+ private final UnixFileSystem fs;
+ private final LinuxWatchService watcher;
+
+ // inotify file descriptor
+ private final int ifd;
+ // socketpair used to shutdown polling thread
+ private final int socketpair[];
+ // maps watch descriptor to Key
+ private final Map<Integer,LinuxWatchKey> wdToKey;
+ // address of read buffer
+ private final long address;
+
+ Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
+ this.fs = fs;
+ this.watcher = watcher;
+ this.ifd = ifd;
+ this.socketpair = sp;
+ this.wdToKey = new HashMap<>();
+ this.address = unsafe.allocateMemory(BUFFER_SIZE);
+ }
+
+ @Override
+ void wakeup() throws IOException {
+ // write to socketpair to wakeup polling thread
+ try {
+ write(socketpair[1], address, 1);
+ } catch (UnixException x) {
+ throw new IOException(x.errorString());
+ }
+ }
+
+ @Override
+ Object implRegister(Path obj,
+ Set<? extends WatchEvent.Kind<?>> events,
+ WatchEvent.Modifier... modifiers)
+ {
+ UnixPath dir = (UnixPath)obj;
+
+ int mask = 0;
+ for (WatchEvent.Kind<?> event: events) {
+ if (event == StandardWatchEventKinds.ENTRY_CREATE) {
+ mask |= IN_CREATE | IN_MOVED_TO;
+ continue;
+ }
+ if (event == StandardWatchEventKinds.ENTRY_DELETE) {
+ mask |= IN_DELETE | IN_MOVED_FROM;
+ continue;
+ }
+ if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
+ mask |= IN_MODIFY | IN_ATTRIB;
+ continue;
+ }
+ }
+
+ // 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");
+ }
+ }
+ }
+
+ // 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());
+ }
+
+ // register with inotify (replaces existing mask if already registered)
+ int wd = -1;
+ try {
+ NativeBuffer buffer =
+ NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());
+ try {
+ wd = inotifyAddWatch(ifd, buffer.address(), mask);
+ } finally {
+ buffer.release();
+ }
+ } catch (UnixException x) {
+ if (x.errno() == ENOSPC) {
+ return new IOException("User limit of inotify watches reached");
+ }
+ return x.asIOException(dir);
+ }
+
+ // ensure watch descriptor is in map
+ LinuxWatchKey key = wdToKey.get(wd);
+ if (key == null) {
+ key = new LinuxWatchKey(dir, watcher, ifd, wd);
+ wdToKey.put(wd, key);
+ }
+ return key;
+ }
+
+ // cancel single key
+ @Override
+ void implCancelKey(WatchKey obj) {
+ LinuxWatchKey key = (LinuxWatchKey)obj;
+ if (key.isValid()) {
+ wdToKey.remove(key.descriptor());
+ key.invalidate(true);
+ }
+ }
+
+ // close watch service
+ @Override
+ void implCloseAll() {
+ // invalidate all keys
+ for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
+ entry.getValue().invalidate(true);
+ }
+ wdToKey.clear();
+
+ // free resources
+ unsafe.freeMemory(address);
+ UnixNativeDispatcher.close(socketpair[0]);
+ UnixNativeDispatcher.close(socketpair[1]);
+ UnixNativeDispatcher.close(ifd);
+ }
+
+ /**
+ * Poller main loop
+ */
+ @Override
+ public void run() {
+ try {
+ for (;;) {
+ int nReady, bytesRead;
+
+ // wait for close or inotify event
+ nReady = poll(ifd, socketpair[0]);
+
+ // read from inotify
+ try {
+ bytesRead = read(ifd, address, BUFFER_SIZE);
+ } catch (UnixException x) {
+ if (x.errno() != EAGAIN)
+ throw x;
+ bytesRead = 0;
+ }
+
+ // iterate over buffer to decode events
+ int offset = 0;
+ while (offset < bytesRead) {
+ long event = address + offset;
+ int wd = unsafe.getInt(event + OFFSETOF_WD);
+ int mask = unsafe.getInt(event + OFFSETOF_MASK);
+ int len = unsafe.getInt(event + OFFSETOF_LEN);
+
+ // file name
+ UnixPath name = null;
+ if (len > 0) {
+ int actual = len;
+
+ // null-terminated and maybe additional null bytes to
+ // align the next event
+ while (actual > 0) {
+ long last = event + OFFSETOF_NAME + actual - 1;
+ if (unsafe.getByte(last) != 0)
+ break;
+ actual--;
+ }
+ if (actual > 0) {
+ byte[] buf = new byte[actual];
+ unsafe.copyMemory(null, event + OFFSETOF_NAME,
+ buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
+ name = new UnixPath(fs, buf);
+ }
+ }
+
+ // process event
+ processEvent(wd, mask, name);
+
+ offset += (SIZEOF_INOTIFY_EVENT + len);
+ }
+
+ // process any pending requests
+ if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
+ try {
+ read(socketpair[0], address, BUFFER_SIZE);
+ boolean shutdown = processRequests();
+ if (shutdown)
+ break;
+ } catch (UnixException x) {
+ if (x.errno() != UnixConstants.EAGAIN)
+ throw x;
+ }
+ }
+ }
+ } 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<>() {
+ public Void run() {
+ System.loadLibrary("nio");
+ return null;
+ }});
+ }
+}