Path: blob/master/src/java.base/linux/classes/sun/nio/fs/LinuxWatchService.java
40948 views
/*1* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.nio.fs;2627import java.nio.file.*;28import java.util.*;29import java.io.IOException;30import jdk.internal.misc.Unsafe;3132import static sun.nio.fs.UnixNativeDispatcher.*;33import static sun.nio.fs.UnixConstants.*;3435/**36* Linux implementation of WatchService based on inotify.37*38* In summary a background thread polls inotify plus a socket used for the wakeup39* mechanism. Requests to add or remove a watch, or close the watch service,40* cause the thread to wakeup and process the request. Events are processed41* by the thread which causes it to signal/queue the corresponding watch keys.42*/4344class LinuxWatchService45extends AbstractWatchService46{47private static final Unsafe unsafe = Unsafe.getUnsafe();4849// background thread to read change events50private final Poller poller;5152LinuxWatchService(UnixFileSystem fs) throws IOException {53// initialize inotify54int ifd = - 1;55try {56ifd = inotifyInit();57} catch (UnixException x) {58String msg = (x.errno() == EMFILE) ?59"User limit of inotify instances reached or too many open files" :60x.errorString();61throw new IOException(msg);62}6364// configure inotify to be non-blocking65// create socketpair used in the close mechanism66int sp[] = new int[2];67try {68configureBlocking(ifd, false);69socketpair(sp);70configureBlocking(sp[0], false);71} catch (UnixException x) {72UnixNativeDispatcher.close(ifd);73throw new IOException(x.errorString());74}7576this.poller = new Poller(fs, this, ifd, sp);77this.poller.start();78}7980@Override81WatchKey register(Path dir,82WatchEvent.Kind<?>[] events,83WatchEvent.Modifier... modifiers)84throws IOException85{86// delegate to poller87return poller.register(dir, events, modifiers);88}8990@Override91void implClose() throws IOException {92// delegate to poller93poller.close();94}9596/**97* WatchKey implementation98*/99private static class LinuxWatchKey extends AbstractWatchKey {100// inotify descriptor101private final int ifd;102// watch descriptor103private volatile int wd;104105LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {106super(dir, watcher);107this.ifd = ifd;108this.wd = wd;109}110111int descriptor() {112return wd;113}114115void invalidate(boolean remove) {116if (remove) {117try {118inotifyRmWatch(ifd, wd);119} catch (UnixException x) {120// ignore121}122}123wd = -1;124}125126@Override127public boolean isValid() {128return (wd != -1);129}130131@Override132public void cancel() {133if (isValid()) {134// delegate to poller135((LinuxWatchService)watcher()).poller.cancel(this);136}137}138}139140/**141* Background thread to read from inotify142*/143private static class Poller extends AbstractPoller {144/**145* struct inotify_event {146* int wd;147* uint32_t mask;148* uint32_t len;149* char name __flexarr; // present if len > 0150* } act_t;151*/152private static final int SIZEOF_INOTIFY_EVENT = eventSize();153private static final int[] offsets = eventOffsets();154private static final int OFFSETOF_WD = offsets[0];155private static final int OFFSETOF_MASK = offsets[1];156private static final int OFFSETOF_LEN = offsets[3];157private static final int OFFSETOF_NAME = offsets[4];158159private static final int IN_MODIFY = 0x00000002;160private static final int IN_ATTRIB = 0x00000004;161private static final int IN_MOVED_FROM = 0x00000040;162private static final int IN_MOVED_TO = 0x00000080;163private static final int IN_CREATE = 0x00000100;164private static final int IN_DELETE = 0x00000200;165166private static final int IN_UNMOUNT = 0x00002000;167private static final int IN_Q_OVERFLOW = 0x00004000;168private static final int IN_IGNORED = 0x00008000;169170// sizeof buffer for when polling inotify171private static final int BUFFER_SIZE = 8192;172173private final UnixFileSystem fs;174private final LinuxWatchService watcher;175176// inotify file descriptor177private final int ifd;178// socketpair used to shutdown polling thread179private final int socketpair[];180// maps watch descriptor to Key181private final Map<Integer,LinuxWatchKey> wdToKey;182// address of read buffer183private final long address;184185Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {186this.fs = fs;187this.watcher = watcher;188this.ifd = ifd;189this.socketpair = sp;190this.wdToKey = new HashMap<>();191this.address = unsafe.allocateMemory(BUFFER_SIZE);192}193194@Override195void wakeup() throws IOException {196// write to socketpair to wakeup polling thread197try {198write(socketpair[1], address, 1);199} catch (UnixException x) {200throw new IOException(x.errorString());201}202}203204@Override205Object implRegister(Path obj,206Set<? extends WatchEvent.Kind<?>> events,207WatchEvent.Modifier... modifiers)208{209UnixPath dir = (UnixPath)obj;210211int mask = 0;212for (WatchEvent.Kind<?> event: events) {213if (event == StandardWatchEventKinds.ENTRY_CREATE) {214mask |= IN_CREATE | IN_MOVED_TO;215continue;216}217if (event == StandardWatchEventKinds.ENTRY_DELETE) {218mask |= IN_DELETE | IN_MOVED_FROM;219continue;220}221if (event == StandardWatchEventKinds.ENTRY_MODIFY) {222mask |= IN_MODIFY | IN_ATTRIB;223continue;224}225}226227// no modifiers supported at this time228if (modifiers.length > 0) {229for (WatchEvent.Modifier modifier: modifiers) {230if (modifier == null)231return new NullPointerException();232if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) &&233!ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) &&234!ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {235return new UnsupportedOperationException("Modifier not supported");236}237}238}239240// check file is directory241UnixFileAttributes attrs = null;242try {243attrs = UnixFileAttributes.get(dir, true);244} catch (UnixException x) {245return x.asIOException(dir);246}247if (!attrs.isDirectory()) {248return new NotDirectoryException(dir.getPathForExceptionMessage());249}250251// register with inotify (replaces existing mask if already registered)252int wd = -1;253try {254NativeBuffer buffer =255NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());256try {257wd = inotifyAddWatch(ifd, buffer.address(), mask);258} finally {259buffer.release();260}261} catch (UnixException x) {262if (x.errno() == ENOSPC) {263return new IOException("User limit of inotify watches reached");264}265return x.asIOException(dir);266}267268// ensure watch descriptor is in map269LinuxWatchKey key = wdToKey.get(wd);270if (key == null) {271key = new LinuxWatchKey(dir, watcher, ifd, wd);272wdToKey.put(wd, key);273}274return key;275}276277// cancel single key278@Override279void implCancelKey(WatchKey obj) {280LinuxWatchKey key = (LinuxWatchKey)obj;281if (key.isValid()) {282wdToKey.remove(key.descriptor());283key.invalidate(true);284}285}286287// close watch service288@Override289void implCloseAll() {290// invalidate all keys291for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {292entry.getValue().invalidate(true);293}294wdToKey.clear();295296// free resources297unsafe.freeMemory(address);298UnixNativeDispatcher.close(socketpair[0]);299UnixNativeDispatcher.close(socketpair[1]);300UnixNativeDispatcher.close(ifd);301}302303/**304* Poller main loop305*/306@Override307public void run() {308try {309for (;;) {310int nReady, bytesRead;311312// wait for close or inotify event313nReady = poll(ifd, socketpair[0]);314315// read from inotify316try {317bytesRead = read(ifd, address, BUFFER_SIZE);318} catch (UnixException x) {319if (x.errno() != EAGAIN && x.errno() != EWOULDBLOCK)320throw x;321bytesRead = 0;322}323324// iterate over buffer to decode events325int offset = 0;326while (offset < bytesRead) {327long event = address + offset;328int wd = unsafe.getInt(event + OFFSETOF_WD);329int mask = unsafe.getInt(event + OFFSETOF_MASK);330int len = unsafe.getInt(event + OFFSETOF_LEN);331332// file name333UnixPath name = null;334if (len > 0) {335int actual = len;336337// null-terminated and maybe additional null bytes to338// align the next event339while (actual > 0) {340long last = event + OFFSETOF_NAME + actual - 1;341if (unsafe.getByte(last) != 0)342break;343actual--;344}345if (actual > 0) {346byte[] buf = new byte[actual];347unsafe.copyMemory(null, event + OFFSETOF_NAME,348buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);349name = new UnixPath(fs, buf);350}351}352353// process event354processEvent(wd, mask, name);355356offset += (SIZEOF_INOTIFY_EVENT + len);357}358359// process any pending requests360if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {361try {362read(socketpair[0], address, BUFFER_SIZE);363boolean shutdown = processRequests();364if (shutdown)365break;366} catch (UnixException x) {367if (x.errno() != EAGAIN && x.errno() != EWOULDBLOCK)368throw x;369}370}371}372} catch (UnixException x) {373x.printStackTrace();374}375}376377378/**379* map inotify event to WatchEvent.Kind380*/381private WatchEvent.Kind<?> maskToEventKind(int mask) {382if ((mask & IN_MODIFY) > 0)383return StandardWatchEventKinds.ENTRY_MODIFY;384if ((mask & IN_ATTRIB) > 0)385return StandardWatchEventKinds.ENTRY_MODIFY;386if ((mask & IN_CREATE) > 0)387return StandardWatchEventKinds.ENTRY_CREATE;388if ((mask & IN_MOVED_TO) > 0)389return StandardWatchEventKinds.ENTRY_CREATE;390if ((mask & IN_DELETE) > 0)391return StandardWatchEventKinds.ENTRY_DELETE;392if ((mask & IN_MOVED_FROM) > 0)393return StandardWatchEventKinds.ENTRY_DELETE;394return null;395}396397/**398* Process event from inotify399*/400private void processEvent(int wd, int mask, final UnixPath name) {401// overflow - signal all keys402if ((mask & IN_Q_OVERFLOW) > 0) {403for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {404entry.getValue()405.signalEvent(StandardWatchEventKinds.OVERFLOW, null);406}407return;408}409410// lookup wd to get key411LinuxWatchKey key = wdToKey.get(wd);412if (key == null)413return; // should not happen414415// file deleted416if ((mask & IN_IGNORED) > 0) {417wdToKey.remove(wd);418key.invalidate(false);419key.signal();420return;421}422423// event for directory itself424if (name == null)425return;426427// map to event and queue to key428WatchEvent.Kind<?> kind = maskToEventKind(mask);429if (kind != null) {430key.signalEvent(kind, name);431}432}433}434435// -- native methods --436437// sizeof inotify_event438private static native int eventSize();439440// offsets of inotify_event441private static native int[] eventOffsets();442443private static native int inotifyInit() throws UnixException;444445private static native int inotifyAddWatch(int fd, long pathAddress, int mask)446throws UnixException;447448private static native void inotifyRmWatch(int fd, int wd)449throws UnixException;450451private static native void configureBlocking(int fd, boolean blocking)452throws UnixException;453454private static native void socketpair(int[] sv) throws UnixException;455456private static native int poll(int fd1, int fd2) throws UnixException;457458static {459jdk.internal.loader.BootLoader.loadLibrary("nio");460}461}462463464