1 /*
   2  * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import java.util.*;
  30 import java.io.IOException;
  31 import jdk.internal.misc.Unsafe;
  32 
  33 import static sun.nio.fs.UnixNativeDispatcher.*;
  34 import static sun.nio.fs.UnixConstants.*;
  35 
  36 /**
  37  * Linux implementation of WatchService based on inotify.
  38  *
  39  * In summary a background thread polls inotify plus a socket used for the wakeup
  40  * mechanism. Requests to add or remove a watch, or close the watch service,
  41  * cause the thread to wakeup and process the request. Events are processed
  42  * by the thread which causes it to signal/queue the corresponding watch keys.
  43  */
  44 
  45 class LinuxWatchService
  46     extends AbstractWatchService
  47 {
  48     private static final Unsafe unsafe = Unsafe.getUnsafe();
  49 
  50     // background thread to read change events
  51     private final Poller poller;
  52 
  53     LinuxWatchService(UnixFileSystem fs) throws IOException {
  54         // initialize inotify
  55         int ifd = - 1;
  56         try {
  57             ifd = inotifyInit();
  58         } catch (UnixException x) {
  59             String msg = (x.errno() == EMFILE) ?
  60                 "User limit of inotify instances reached or too many open files" :
  61                 x.errorString();
  62             throw new IOException(msg);
  63         }
  64 
  65         // configure inotify to be non-blocking
  66         // create socketpair used in the close mechanism
  67         int sp[] = new int[2];
  68         try {
  69             configureBlocking(ifd, false);
  70             socketpair(sp);
  71             configureBlocking(sp[0], false);
  72         } catch (UnixException x) {
  73             UnixNativeDispatcher.close(ifd);
  74             throw new IOException(x.errorString());
  75         }
  76 
  77         this.poller = new Poller(fs, this, ifd, sp);
  78         this.poller.start();
  79     }
  80 
  81     @Override
  82     WatchKey register(Path dir,
  83                       WatchEvent.Kind<?>[] events,
  84                       WatchEvent.Modifier... modifiers)
  85          throws IOException
  86     {
  87         // delegate to poller
  88         return poller.register(dir, events, modifiers);
  89     }
  90 
  91     @Override
  92     void implClose() throws IOException {
  93         // delegate to poller
  94         poller.close();
  95     }
  96 
  97     /**
  98      * WatchKey implementation
  99      */
 100     private static class LinuxWatchKey extends AbstractWatchKey {
 101         // inotify descriptor
 102         private final int ifd;
 103         // watch descriptor
 104         private volatile int wd;
 105 
 106         LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {
 107             super(dir, watcher);
 108             this.ifd = ifd;
 109             this.wd = wd;
 110         }
 111 
 112         int descriptor() {
 113             return wd;
 114         }
 115 
 116         void invalidate(boolean remove) {
 117             if (remove) {
 118                 try {
 119                     inotifyRmWatch(ifd, wd);
 120                 } catch (UnixException x) {
 121                     // ignore
 122                 }
 123             }
 124             wd = -1;
 125         }
 126 
 127         @Override
 128         public boolean isValid() {
 129             return (wd != -1);
 130         }
 131 
 132         @Override
 133         public void cancel() {
 134             if (isValid()) {
 135                 // delegate to poller
 136                 ((LinuxWatchService)watcher()).poller.cancel(this);
 137             }
 138         }
 139     }
 140 
 141     /**
 142      * Background thread to read from inotify
 143      */
 144     private static class Poller extends AbstractPoller {
 145         /**
 146          * struct inotify_event {
 147          *     int          wd;
 148          *     uint32_t     mask;
 149          *     uint32_t     len;
 150          *     char name    __flexarr;  // present if len > 0
 151          * } act_t;
 152          */
 153         private static final int SIZEOF_INOTIFY_EVENT  = eventSize();
 154         private static final int[] offsets             = eventOffsets();
 155         private static final int OFFSETOF_WD           = offsets[0];
 156         private static final int OFFSETOF_MASK         = offsets[1];
 157         private static final int OFFSETOF_LEN          = offsets[3];
 158         private static final int OFFSETOF_NAME         = offsets[4];
 159 
 160         private static final int IN_MODIFY          = 0x00000002;
 161         private static final int IN_ATTRIB          = 0x00000004;
 162         private static final int IN_MOVED_FROM      = 0x00000040;
 163         private static final int IN_MOVED_TO        = 0x00000080;
 164         private static final int IN_CREATE          = 0x00000100;
 165         private static final int IN_DELETE          = 0x00000200;
 166 
 167         private static final int IN_UNMOUNT         = 0x00002000;
 168         private static final int IN_Q_OVERFLOW      = 0x00004000;
 169         private static final int IN_IGNORED         = 0x00008000;
 170 
 171         // sizeof buffer for when polling inotify
 172         private static final int BUFFER_SIZE = 8192;
 173 
 174         private final UnixFileSystem fs;
 175         private final LinuxWatchService watcher;
 176 
 177         // inotify file descriptor
 178         private final int ifd;
 179         // socketpair used to shutdown polling thread
 180         private final int socketpair[];
 181         // maps watch descriptor to Key
 182         private final Map<Integer,LinuxWatchKey> wdToKey;
 183         // address of read buffer
 184         private final long address;
 185 
 186         Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
 187             this.fs = fs;
 188             this.watcher = watcher;
 189             this.ifd = ifd;
 190             this.socketpair = sp;
 191             this.wdToKey = new HashMap<>();
 192             this.address = unsafe.allocateMemory(BUFFER_SIZE);
 193         }
 194 
 195         @Override
 196         void wakeup() throws IOException {
 197             // write to socketpair to wakeup polling thread
 198             try {
 199                 write(socketpair[1], address, 1);
 200             } catch (UnixException x) {
 201                 throw new IOException(x.errorString());
 202             }
 203         }
 204 
 205         @Override
 206         Object implRegister(Path obj,
 207                             Set<? extends WatchEvent.Kind<?>> events,
 208                             WatchEvent.Modifier... modifiers)
 209         {
 210             UnixPath dir = (UnixPath)obj;
 211 
 212             int mask = 0;
 213             for (WatchEvent.Kind<?> event: events) {
 214                 if (event == StandardWatchEventKinds.ENTRY_CREATE) {
 215                     mask |= IN_CREATE | IN_MOVED_TO;
 216                     continue;
 217                 }
 218                 if (event == StandardWatchEventKinds.ENTRY_DELETE) {
 219                     mask |= IN_DELETE | IN_MOVED_FROM;
 220                     continue;
 221                 }
 222                 if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
 223                     mask |= IN_MODIFY | IN_ATTRIB;
 224                     continue;
 225                 }
 226             }
 227 
 228             // no modifiers supported at this time
 229             if (modifiers.length > 0) {
 230                 for (WatchEvent.Modifier modifier: modifiers) {
 231                     if (modifier == null)
 232                         return new NullPointerException();
 233                     if (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) &&
 234                             !ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) &&
 235                             !ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {
 236                         return new UnsupportedOperationException("Modifier not supported");
 237                     }
 238                 }
 239             }
 240 
 241             // check file is directory
 242             UnixFileAttributes attrs = null;
 243             try {
 244                 attrs = UnixFileAttributes.get(dir, true);
 245             } catch (UnixException x) {
 246                 return x.asIOException(dir);
 247             }
 248             if (!attrs.isDirectory()) {
 249                 return new NotDirectoryException(dir.getPathForExceptionMessage());
 250             }
 251 
 252             // register with inotify (replaces existing mask if already registered)
 253             int wd = -1;
 254             try {
 255                 NativeBuffer buffer =
 256                     NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());
 257                 try {
 258                     wd = inotifyAddWatch(ifd, buffer.address(), mask);
 259                 } finally {
 260                     buffer.release();
 261                 }
 262             } catch (UnixException x) {
 263                 if (x.errno() == ENOSPC) {
 264                     return new IOException("User limit of inotify watches reached");
 265                 }
 266                 return x.asIOException(dir);
 267             }
 268 
 269             // ensure watch descriptor is in map
 270             LinuxWatchKey key = wdToKey.get(wd);
 271             if (key == null) {
 272                 key = new LinuxWatchKey(dir, watcher, ifd, wd);
 273                 wdToKey.put(wd, key);
 274             }
 275             return key;
 276         }
 277 
 278         // cancel single key
 279         @Override
 280         void implCancelKey(WatchKey obj) {
 281             LinuxWatchKey key = (LinuxWatchKey)obj;
 282             if (key.isValid()) {
 283                 wdToKey.remove(key.descriptor());
 284                 key.invalidate(true);
 285             }
 286         }
 287 
 288         // close watch service
 289         @Override
 290         void implCloseAll() {
 291             // invalidate all keys
 292             for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
 293                 entry.getValue().invalidate(true);
 294             }
 295             wdToKey.clear();
 296 
 297             // free resources
 298             unsafe.freeMemory(address);
 299             UnixNativeDispatcher.close(socketpair[0]);
 300             UnixNativeDispatcher.close(socketpair[1]);
 301             UnixNativeDispatcher.close(ifd);
 302         }
 303 
 304         /**
 305          * Poller main loop
 306          */
 307         @Override
 308         public void run() {
 309             try {
 310                 for (;;) {
 311                     int nReady, bytesRead;
 312 
 313                     // wait for close or inotify event
 314                     nReady = poll(ifd, socketpair[0]);
 315 
 316                     // read from inotify
 317                     try {
 318                         bytesRead = read(ifd, address, BUFFER_SIZE);
 319                     } catch (UnixException x) {
 320                         if (x.errno() != EAGAIN && x.errno() != EWOULDBLOCK)
 321                             throw x;
 322                         bytesRead = 0;
 323                     }
 324 
 325                     // iterate over buffer to decode events
 326                     int offset = 0;
 327                     while (offset < bytesRead) {
 328                         long event = address + offset;
 329                         int wd = unsafe.getInt(event + OFFSETOF_WD);
 330                         int mask = unsafe.getInt(event + OFFSETOF_MASK);
 331                         int len = unsafe.getInt(event + OFFSETOF_LEN);
 332 
 333                         // file name
 334                         UnixPath name = null;
 335                         if (len > 0) {
 336                             int actual = len;
 337 
 338                             // null-terminated and maybe additional null bytes to
 339                             // align the next event
 340                             while (actual > 0) {
 341                                 long last = event + OFFSETOF_NAME + actual - 1;
 342                                 if (unsafe.getByte(last) != 0)
 343                                     break;
 344                                 actual--;
 345                             }
 346                             if (actual > 0) {
 347                                 byte[] buf = new byte[actual];
 348                                 unsafe.copyMemory(null, event + OFFSETOF_NAME,
 349                                     buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
 350                                 name = new UnixPath(fs, buf);
 351                             }
 352                         }
 353 
 354                         // process event
 355                         processEvent(wd, mask, name);
 356 
 357                         offset += (SIZEOF_INOTIFY_EVENT + len);
 358                     }
 359 
 360                     // process any pending requests
 361                     if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
 362                         try {
 363                             read(socketpair[0], address, BUFFER_SIZE);
 364                             boolean shutdown = processRequests();
 365                             if (shutdown)
 366                                 break;
 367                         } catch (UnixException x) {
 368                             if (x.errno() != EAGAIN && x.errno() != EWOULDBLOCK)
 369                                 throw x;
 370                         }
 371                     }
 372                 }
 373             } catch (UnixException x) {
 374                 x.printStackTrace();
 375             }
 376         }
 377 
 378 
 379         /**
 380          * map inotify event to WatchEvent.Kind
 381          */
 382         private WatchEvent.Kind<?> maskToEventKind(int mask) {
 383             if ((mask & IN_MODIFY) > 0)
 384                 return StandardWatchEventKinds.ENTRY_MODIFY;
 385             if ((mask & IN_ATTRIB) > 0)
 386                 return StandardWatchEventKinds.ENTRY_MODIFY;
 387             if ((mask & IN_CREATE) > 0)
 388                 return StandardWatchEventKinds.ENTRY_CREATE;
 389             if ((mask & IN_MOVED_TO) > 0)
 390                 return StandardWatchEventKinds.ENTRY_CREATE;
 391             if ((mask & IN_DELETE) > 0)
 392                 return StandardWatchEventKinds.ENTRY_DELETE;
 393             if ((mask & IN_MOVED_FROM) > 0)
 394                 return StandardWatchEventKinds.ENTRY_DELETE;
 395             return null;
 396         }
 397 
 398         /**
 399          * Process event from inotify
 400          */
 401         private void processEvent(int wd, int mask, final UnixPath name) {
 402             // overflow - signal all keys
 403             if ((mask & IN_Q_OVERFLOW) > 0) {
 404                 for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
 405                     entry.getValue()
 406                         .signalEvent(StandardWatchEventKinds.OVERFLOW, null);
 407                 }
 408                 return;
 409             }
 410 
 411             // lookup wd to get key
 412             LinuxWatchKey key = wdToKey.get(wd);
 413             if (key == null)
 414                 return; // should not happen
 415 
 416             // file deleted
 417             if ((mask & IN_IGNORED) > 0) {
 418                 wdToKey.remove(wd);
 419                 key.invalidate(false);
 420                 key.signal();
 421                 return;
 422             }
 423 
 424             // event for directory itself
 425             if (name == null)
 426                 return;
 427 
 428             // map to event and queue to key
 429             WatchEvent.Kind<?> kind = maskToEventKind(mask);
 430             if (kind != null) {
 431                 key.signalEvent(kind, name);
 432             }
 433         }
 434     }
 435 
 436     // -- native methods --
 437 
 438     // sizeof inotify_event
 439     private static native int eventSize();
 440 
 441     // offsets of inotify_event
 442     private static native int[] eventOffsets();
 443 
 444     private static native int inotifyInit() throws UnixException;
 445 
 446     private static native int inotifyAddWatch(int fd, long pathAddress, int mask)
 447         throws UnixException;
 448 
 449     private static native void inotifyRmWatch(int fd, int wd)
 450         throws UnixException;
 451 
 452     private static native void configureBlocking(int fd, boolean blocking)
 453         throws UnixException;
 454 
 455     private static native void socketpair(int[] sv) throws UnixException;
 456 
 457     private static native int poll(int fd1, int fd2) throws UnixException;
 458 
 459     static {
 460         jdk.internal.access.SharedSecrets.getJavaLangAccess().loadLibrary("nio");
 461     }
 462 }