src/java.base/linux/classes/sun/nio/fs/LinuxWatchService.java
changeset 47216 71c04702a3d5
parent 38742 4af80d6e9e4d
child 50275 69204b98dc3d
--- /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;
+        }});
+    }
+}