1 /*
   2  * Copyright (c) 2008, 2012, 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.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import java.util.*;
  32 import java.io.IOException;
  33 import jdk.internal.misc.Unsafe;
  34 
  35 import static sun.nio.fs.UnixNativeDispatcher.*;
  36 import static sun.nio.fs.UnixConstants.*;
  37 
  38 /**
  39  * Linux implementation of WatchService based on inotify.
  40  *
  41  * In summary a background thread polls inotify plus a socket used for the wakeup
  42  * mechanism. Requests to add or remove a watch, or close the watch service,
  43  * cause the thread to wakeup and process the request. Events are processed
  44  * by the thread which causes it to signal/queue the corresponding watch keys.
  45  */
  46 
  47 class LinuxWatchService
  48     extends AbstractWatchService
  49 {
  50     private static final Unsafe unsafe = Unsafe.getUnsafe();
  51 
  52     // background thread to read change events
  53     private final Poller poller;
  54 
  55     LinuxWatchService(UnixFileSystem fs) throws IOException {
  56         // initialize inotify
  57         int ifd = - 1;
  58         try {
  59             ifd = inotifyInit();
  60         } catch (UnixException x) {
  61             String msg = (x.errno() == EMFILE) ?
  62                 "User limit of inotify instances reached or too many open files" :
  63                 x.errorString();
  64             throw new IOException(msg);
  65         }
  66 
  67         // configure inotify to be non-blocking
  68         // create socketpair used in the close mechanism
  69         int sp[] = new int[2];
  70         try {
  71             configureBlocking(ifd, false);
  72             socketpair(sp);
  73             configureBlocking(sp[0], false);
  74         } catch (UnixException x) {
  75             UnixNativeDispatcher.close(ifd);
  76             throw new IOException(x.errorString());
  77         }
  78 
  79         this.poller = new Poller(fs, this, ifd, sp);
  80         this.poller.start();
  81     }
  82 
  83     @Override
  84     WatchKey register(Path dir,
  85                       WatchEvent.Kind<?>[] events,
  86                       WatchEvent.Modifier... modifiers)
  87          throws IOException
  88     {
  89         // delegate to poller
  90         return poller.register(dir, events, modifiers);
  91     }
  92 
  93     @Override
  94     void implClose() throws IOException {
  95         // delegate to poller
  96         poller.close();
  97     }
  98 
  99     /**
 100      * WatchKey implementation
 101      */
 102     private static class LinuxWatchKey extends AbstractWatchKey {
 103         // inotify descriptor
 104         private final int ifd;
 105         // watch descriptor
 106         private volatile int wd;
 107 
 108         LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {
 109             super(dir, watcher);
 110             this.ifd = ifd;
 111             this.wd = wd;
 112         }
 113 
 114         int descriptor() {
 115             return wd;
 116         }
 117 
 118         void invalidate(boolean remove) {
 119             if (remove) {
 120                 try {
 121                     inotifyRmWatch(ifd, wd);
 122                 } catch (UnixException x) {
 123                     // ignore
 124                 }
 125             }
 126             wd = -1;
 127         }
 128 
 129         @Override
 130         public boolean isValid() {
 131             return (wd != -1);
 132         }
 133 
 134         @Override
 135         public void cancel() {
 136             if (isValid()) {
 137                 // delegate to poller
 138                 ((LinuxWatchService)watcher()).poller.cancel(this);
 139             }
 140         }
 141     }
 142 
 143     /**
 144      * Background thread to read from inotify
 145      */
 146     private static class Poller extends AbstractPoller {
 147         /**
 148          * struct inotify_event {
 149          *     int          wd;
 150          *     uint32_t     mask;
 151          *     uint32_t     len;
 152          *     char name    __flexarr;  // present if len > 0
 153          * } act_t;
 154          */
 155         private static final int SIZEOF_INOTIFY_EVENT  = eventSize();
 156         private static final int[] offsets             = eventOffsets();
 157         private static final int OFFSETOF_WD           = offsets[0];
 158         private static final int OFFSETOF_MASK         = offsets[1];
 159         private static final int OFFSETOF_LEN          = offsets[3];
 160         private static final int OFFSETOF_NAME         = offsets[4];
 161 
 162         private static final int IN_MODIFY          = 0x00000002;
 163         private static final int IN_ATTRIB          = 0x00000004;
 164         private static final int IN_MOVED_FROM      = 0x00000040;
 165         private static final int IN_MOVED_TO        = 0x00000080;
 166         private static final int IN_CREATE          = 0x00000100;
 167         private static final int IN_DELETE          = 0x00000200;
 168 
 169         private static final int IN_UNMOUNT         = 0x00002000;
 170         private static final int IN_Q_OVERFLOW      = 0x00004000;
 171         private static final int IN_IGNORED         = 0x00008000;
 172 
 173         // sizeof buffer for when polling inotify
 174         private static final int BUFFER_SIZE = 8192;
 175 
 176         private final UnixFileSystem fs;
 177         private final LinuxWatchService watcher;
 178 
 179         // inotify file descriptor
 180         private final int ifd;
 181         // socketpair used to shutdown polling thread
 182         private final int socketpair[];
 183         // maps watch descriptor to Key
 184         private final Map<Integer,LinuxWatchKey> wdToKey;
 185         // address of read buffer
 186         private final long address;
 187 
 188         Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
 189             this.fs = fs;
 190             this.watcher = watcher;
 191             this.ifd = ifd;
 192             this.socketpair = sp;
 193             this.wdToKey = new HashMap<>();
 194             this.address = unsafe.allocateMemory(BUFFER_SIZE);
 195         }
 196 
 197         @Override
 198         void wakeup() throws IOException {
 199             // write to socketpair to wakeup polling thread
 200             try {
 201                 write(socketpair[1], address, 1);
 202             } catch (UnixException x) {
 203                 throw new IOException(x.errorString());
 204             }
 205         }
 206 
 207         @Override
 208         Object implRegister(Path obj,
 209                             Set<? extends WatchEvent.Kind<?>> events,
 210                             WatchEvent.Modifier... modifiers)
 211         {
 212             UnixPath dir = (UnixPath)obj;
 213 
 214             int mask = 0;
 215             for (WatchEvent.Kind<?> event: events) {
 216                 if (event == StandardWatchEventKinds.ENTRY_CREATE) {
 217                     mask |= IN_CREATE | IN_MOVED_TO;
 218                     continue;
 219                 }
 220                 if (event == StandardWatchEventKinds.ENTRY_DELETE) {
 221                     mask |= IN_DELETE | IN_MOVED_FROM;
 222                     continue;
 223                 }
 224                 if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
 225                     mask |= IN_MODIFY | IN_ATTRIB;
 226                     continue;
 227                 }
 228             }
 229 
 230             // no modifiers supported at this time
 231             if (modifiers.length > 0) {
 232                 for (WatchEvent.Modifier modifier: modifiers) {
 233                     if (modifier == null)
 234                         return new NullPointerException();
 235                     if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
 236                         continue; // ignore
 237                     return new UnsupportedOperationException("Modifier not supported");
 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)
 321                             throw x;
 322                         bytesRead = 0;
 323                     }
 324 
 325                     // process any pending requests
 326                     if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
 327                         try {
 328                             read(socketpair[0], address, BUFFER_SIZE);
 329                             boolean shutdown = processRequests();
 330                             if (shutdown)
 331                                 break;
 332                         } catch (UnixException x) {
 333                             if (x.errno() != UnixConstants.EAGAIN)
 334                                 throw x;
 335                         }
 336                     }
 337 
 338                     // iterate over buffer to decode events
 339                     int offset = 0;
 340                     while (offset < bytesRead) {
 341                         long event = address + offset;
 342                         int wd = unsafe.getInt(event + OFFSETOF_WD);
 343                         int mask = unsafe.getInt(event + OFFSETOF_MASK);
 344                         int len = unsafe.getInt(event + OFFSETOF_LEN);
 345 
 346                         // file name
 347                         UnixPath name = null;
 348                         if (len > 0) {
 349                             int actual = len;
 350 
 351                             // null-terminated and maybe additional null bytes to
 352                             // align the next event
 353                             while (actual > 0) {
 354                                 long last = event + OFFSETOF_NAME + actual - 1;
 355                                 if (unsafe.getByte(last) != 0)
 356                                     break;
 357                                 actual--;
 358                             }
 359                             if (actual > 0) {
 360                                 byte[] buf = new byte[actual];
 361                                 unsafe.copyMemory(null, event + OFFSETOF_NAME,
 362                                     buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
 363                                 name = new UnixPath(fs, buf);
 364                             }
 365                         }
 366 
 367                         // process event
 368                         processEvent(wd, mask, name);
 369 
 370                         offset += (SIZEOF_INOTIFY_EVENT + len);
 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         AccessController.doPrivileged(new PrivilegedAction<>() {
 461             public Void run() {
 462                 System.loadLibrary("nio");
 463                 return null;
 464         }});
 465     }
 466 }