jdk/src/solaris/classes/sun/nio/fs/LinuxWatchService.java
changeset 2057 3acf8e5e2ca0
child 5506 202f599c92aa
equal deleted inserted replaced
2056:115e09b7a004 2057:3acf8e5e2ca0
       
     1 /*
       
     2  * Copyright 2008-2009 Sun Microsystems, Inc.  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.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any 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 sun.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             throw new IOException(x.errorString());
       
    62         }
       
    63 
       
    64         // configure inotify to be non-blocking
       
    65         // create socketpair used in the close mechanism
       
    66         int sp[] = new int[2];
       
    67         try {
       
    68             configureBlocking(ifd, false);
       
    69             socketpair(sp);
       
    70             configureBlocking(sp[0], false);
       
    71         } catch (UnixException x) {
       
    72             UnixNativeDispatcher.close(ifd);
       
    73             throw new IOException(x.errorString());
       
    74         }
       
    75 
       
    76         this.poller = new Poller(fs, this, ifd, sp);
       
    77         this.poller.start();
       
    78     }
       
    79 
       
    80     @Override
       
    81     WatchKey register(Path dir,
       
    82                       WatchEvent.Kind<?>[] events,
       
    83                       WatchEvent.Modifier... modifiers)
       
    84          throws IOException
       
    85     {
       
    86         // delegate to poller
       
    87         return poller.register(dir, events, modifiers);
       
    88     }
       
    89 
       
    90     @Override
       
    91     void implClose() throws IOException {
       
    92         // delegate to poller
       
    93         poller.close();
       
    94     }
       
    95 
       
    96     /**
       
    97      * WatchKey implementation
       
    98      */
       
    99     private static class LinuxWatchKey extends AbstractWatchKey {
       
   100         // inotify descriptor
       
   101         private final int ifd;
       
   102         // watch descriptor
       
   103         private volatile int wd;
       
   104 
       
   105         LinuxWatchKey(LinuxWatchService watcher, int ifd, int wd) {
       
   106             super(watcher);
       
   107             this.ifd = ifd;
       
   108             this.wd = wd;
       
   109         }
       
   110 
       
   111         int descriptor() {
       
   112             return wd;
       
   113         }
       
   114 
       
   115         void invalidate(boolean remove) {
       
   116             if (remove) {
       
   117                 try {
       
   118                     inotifyRmWatch(ifd, wd);
       
   119                 } catch (UnixException x) {
       
   120                     // ignore
       
   121                 }
       
   122             }
       
   123             wd = -1;
       
   124         }
       
   125 
       
   126         @Override
       
   127         public boolean isValid() {
       
   128             return (wd != -1);
       
   129         }
       
   130 
       
   131         @Override
       
   132         public void cancel() {
       
   133             if (isValid()) {
       
   134                 // delegate to poller
       
   135                 ((LinuxWatchService)watcher()).poller.cancel(this);
       
   136             }
       
   137         }
       
   138     }
       
   139 
       
   140     /**
       
   141      * Background thread to read from inotify
       
   142      */
       
   143     private 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 > 0
       
   150          * } act_t;
       
   151          */
       
   152         private static final int SIZEOF_INOTIFY_EVENT  = eventSize();
       
   153         private static final int[] offsets             = eventOffsets();
       
   154         private static final int OFFSETOF_WD           = offsets[0];
       
   155         private static final int OFFSETOF_MASK         = offsets[1];
       
   156         private static final int OFFSETOF_LEN          = offsets[3];
       
   157         private static final int OFFSETOF_NAME         = offsets[4];
       
   158 
       
   159         private static final int IN_MODIFY          = 0x00000002;
       
   160         private static final int IN_ATTRIB          = 0x00000004;
       
   161         private static final int IN_MOVED_FROM      = 0x00000040;
       
   162         private static final int IN_MOVED_TO        = 0x00000080;
       
   163         private static final int IN_CREATE          = 0x00000100;
       
   164         private static final int IN_DELETE          = 0x00000200;
       
   165 
       
   166         private static final int IN_UNMOUNT         = 0x00002000;
       
   167         private static final int IN_Q_OVERFLOW      = 0x00004000;
       
   168         private static final int IN_IGNORED         = 0x00008000;
       
   169 
       
   170         // sizeof buffer for when polling inotify
       
   171         private static final int BUFFER_SIZE = 8192;
       
   172 
       
   173         private final UnixFileSystem fs;
       
   174         private final LinuxWatchService watcher;
       
   175 
       
   176         // inotify file descriptor
       
   177         private final int ifd;
       
   178         // socketpair used to shutdown polling thread
       
   179         private final int socketpair[];
       
   180         // maps watch descriptor to Key
       
   181         private final Map<Integer,LinuxWatchKey> wdToKey;
       
   182         // address of read buffer
       
   183         private final long address;
       
   184 
       
   185         Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
       
   186             this.fs = fs;
       
   187             this.watcher = watcher;
       
   188             this.ifd = ifd;
       
   189             this.socketpair = sp;
       
   190             this.wdToKey = new HashMap<Integer,LinuxWatchKey>();
       
   191             this.address = unsafe.allocateMemory(BUFFER_SIZE);
       
   192         }
       
   193 
       
   194         @Override
       
   195         void wakeup() throws IOException {
       
   196             // write to socketpair to wakeup polling thread
       
   197             try {
       
   198                 write(socketpair[1], address, 1);
       
   199             } catch (UnixException x) {
       
   200                 throw new IOException(x.errorString());
       
   201             }
       
   202         }
       
   203 
       
   204         @Override
       
   205         Object implRegister(Path obj,
       
   206                             Set<? extends WatchEvent.Kind<?>> events,
       
   207                             WatchEvent.Modifier... modifiers)
       
   208         {
       
   209             UnixPath dir = (UnixPath)obj;
       
   210 
       
   211             int mask = 0;
       
   212             for (WatchEvent.Kind<?> event: events) {
       
   213                 if (event == StandardWatchEventKind.ENTRY_CREATE) {
       
   214                     mask |= IN_CREATE | IN_MOVED_TO;
       
   215                     continue;
       
   216                 }
       
   217                 if (event == StandardWatchEventKind.ENTRY_DELETE) {
       
   218                     mask |= IN_DELETE | IN_MOVED_FROM;
       
   219                     continue;
       
   220                 }
       
   221                 if (event == StandardWatchEventKind.ENTRY_MODIFY) {
       
   222                     mask |= IN_MODIFY | IN_ATTRIB;
       
   223                     continue;
       
   224                 }
       
   225             }
       
   226 
       
   227             // no modifiers supported at this time
       
   228             if (modifiers.length > 0) {
       
   229                 for (WatchEvent.Modifier modifier: modifiers) {
       
   230                     if (modifier == null)
       
   231                         return new NullPointerException();
       
   232                     if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
       
   233                         continue; // ignore
       
   234                     return new UnsupportedOperationException("Modifier not supported");
       
   235                 }
       
   236             }
       
   237 
       
   238             // check file is directory
       
   239             UnixFileAttributes attrs = null;
       
   240             try {
       
   241                 attrs = UnixFileAttributes.get(dir, true);
       
   242             } catch (UnixException x) {
       
   243                 return x.asIOException(dir);
       
   244             }
       
   245             if (!attrs.isDirectory()) {
       
   246                 return new NotDirectoryException(dir.getPathForExecptionMessage());
       
   247             }
       
   248 
       
   249             // register with inotify (replaces existing mask if already registered)
       
   250             int wd = -1;
       
   251             try {
       
   252                 NativeBuffer buffer =
       
   253                     NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());
       
   254                 try {
       
   255                     wd = inotifyAddWatch(ifd, buffer.address(), mask);
       
   256                 } finally {
       
   257                     buffer.release();
       
   258                 }
       
   259             } catch (UnixException x) {
       
   260                 if (x.errno() == ENOSPC) {
       
   261                     return new IOException("User limit of inotify watches reached");
       
   262                 }
       
   263                 return x.asIOException(dir);
       
   264             }
       
   265 
       
   266             // ensure watch descriptor is in map
       
   267             LinuxWatchKey key = wdToKey.get(wd);
       
   268             if (key == null) {
       
   269                 key = new LinuxWatchKey(watcher, ifd, wd);
       
   270                 wdToKey.put(wd, key);
       
   271             }
       
   272             return key;
       
   273         }
       
   274 
       
   275         // cancel single key
       
   276         @Override
       
   277         void implCancelKey(WatchKey obj) {
       
   278             LinuxWatchKey key = (LinuxWatchKey)obj;
       
   279             if (key.isValid()) {
       
   280                 wdToKey.remove(key.descriptor());
       
   281                 key.invalidate(true);
       
   282             }
       
   283         }
       
   284 
       
   285         // close watch service
       
   286         @Override
       
   287         void implCloseAll() {
       
   288             // invalidate all keys
       
   289             for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
       
   290                 entry.getValue().invalidate(true);
       
   291             }
       
   292             wdToKey.clear();
       
   293 
       
   294             // free resources
       
   295             unsafe.freeMemory(address);
       
   296             UnixNativeDispatcher.close(socketpair[0]);
       
   297             UnixNativeDispatcher.close(socketpair[1]);
       
   298             UnixNativeDispatcher.close(ifd);
       
   299         }
       
   300 
       
   301         /**
       
   302          * Poller main loop
       
   303          */
       
   304         @Override
       
   305         public void run() {
       
   306             try {
       
   307                 for (;;) {
       
   308                     int nReady, bytesRead;
       
   309 
       
   310                     // wait for close or inotify event
       
   311                     nReady = poll(ifd, socketpair[0]);
       
   312 
       
   313                     // read from inotify
       
   314                     try {
       
   315                         bytesRead = read(ifd, address, BUFFER_SIZE);
       
   316                     } catch (UnixException x) {
       
   317                         if (x.errno() != EAGAIN)
       
   318                             throw x;
       
   319                         bytesRead = 0;
       
   320                     }
       
   321 
       
   322                     // process any pending requests
       
   323                     if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
       
   324                         try {
       
   325                             read(socketpair[0], address, BUFFER_SIZE);
       
   326                             boolean shutdown = processRequests();
       
   327                             if (shutdown)
       
   328                                 break;
       
   329                         } catch (UnixException x) {
       
   330                             if (x.errno() != UnixConstants.EAGAIN)
       
   331                                 throw x;
       
   332                         }
       
   333                     }
       
   334 
       
   335                     // iterate over buffer to decode events
       
   336                     int offset = 0;
       
   337                     while (offset < bytesRead) {
       
   338                         long event = address + offset;
       
   339                         int wd = unsafe.getInt(event + OFFSETOF_WD);
       
   340                         int mask = unsafe.getInt(event + OFFSETOF_MASK);
       
   341                         int len = unsafe.getInt(event + OFFSETOF_LEN);
       
   342 
       
   343                         // file name
       
   344                         UnixPath name = null;
       
   345                         if (len > 0) {
       
   346                             int actual = len;
       
   347 
       
   348                             // null-terminated and maybe additional null bytes to
       
   349                             // align the next event
       
   350                             while (actual > 0) {
       
   351                                 long last = event + OFFSETOF_NAME + actual - 1;
       
   352                                 if (unsafe.getByte(last) != 0)
       
   353                                     break;
       
   354                                 actual--;
       
   355                             }
       
   356                             if (actual > 0) {
       
   357                                 byte[] buf = new byte[actual];
       
   358                                 unsafe.copyMemory(null, event + OFFSETOF_NAME,
       
   359                                     buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
       
   360                                 name = new UnixPath(fs, buf);
       
   361                             }
       
   362                         }
       
   363 
       
   364                         // process event
       
   365                         processEvent(wd, mask, name);
       
   366 
       
   367                         offset += (SIZEOF_INOTIFY_EVENT + len);
       
   368                     }
       
   369                 }
       
   370             } catch (UnixException x) {
       
   371                 x.printStackTrace();
       
   372             }
       
   373         }
       
   374 
       
   375 
       
   376         /**
       
   377          * map inotify event to WatchEvent.Kind
       
   378          */
       
   379         private WatchEvent.Kind<?> maskToEventKind(int mask) {
       
   380             if ((mask & IN_MODIFY) > 0)
       
   381                 return StandardWatchEventKind.ENTRY_MODIFY;
       
   382             if ((mask & IN_ATTRIB) > 0)
       
   383                 return StandardWatchEventKind.ENTRY_MODIFY;
       
   384             if ((mask & IN_CREATE) > 0)
       
   385                 return StandardWatchEventKind.ENTRY_CREATE;
       
   386             if ((mask & IN_MOVED_TO) > 0)
       
   387                 return StandardWatchEventKind.ENTRY_CREATE;
       
   388             if ((mask & IN_DELETE) > 0)
       
   389                 return StandardWatchEventKind.ENTRY_DELETE;
       
   390             if ((mask & IN_MOVED_FROM) > 0)
       
   391                 return StandardWatchEventKind.ENTRY_DELETE;
       
   392             return null;
       
   393         }
       
   394 
       
   395         /**
       
   396          * Process event from inotify
       
   397          */
       
   398         private void processEvent(int wd, int mask, final UnixPath name) {
       
   399             // overflow - signal all keys
       
   400             if ((mask & IN_Q_OVERFLOW) > 0) {
       
   401                 for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
       
   402                     entry.getValue()
       
   403                         .signalEvent(StandardWatchEventKind.OVERFLOW, null);
       
   404                 }
       
   405                 return;
       
   406             }
       
   407 
       
   408             // lookup wd to get key
       
   409             LinuxWatchKey key = wdToKey.get(wd);
       
   410             if (key == null)
       
   411                 return; // should not happen
       
   412 
       
   413             // file deleted
       
   414             if ((mask & IN_IGNORED) > 0) {
       
   415                 wdToKey.remove(wd);
       
   416                 key.invalidate(false);
       
   417                 key.signal();
       
   418                 return;
       
   419             }
       
   420 
       
   421             // event for directory itself
       
   422             if (name == null)
       
   423                 return;
       
   424 
       
   425             // map to event and queue to key
       
   426             WatchEvent.Kind<?> kind = maskToEventKind(mask);
       
   427             if (kind != null) {
       
   428                 key.signalEvent(kind, name);
       
   429             }
       
   430         }
       
   431     }
       
   432 
       
   433     // -- native methods --
       
   434 
       
   435     private static native void init();
       
   436 
       
   437     // sizeof inotify_event
       
   438     private static native int eventSize();
       
   439 
       
   440     // offsets of inotify_event
       
   441     private static native int[] eventOffsets();
       
   442 
       
   443     private static native int inotifyInit() throws UnixException;
       
   444 
       
   445     private static native int inotifyAddWatch(int fd, long pathAddress, int mask)
       
   446         throws UnixException;
       
   447 
       
   448     private static native void inotifyRmWatch(int fd, int wd)
       
   449         throws UnixException;
       
   450 
       
   451     private static native void configureBlocking(int fd, boolean blocking)
       
   452         throws UnixException;
       
   453 
       
   454     private static native void socketpair(int[] sv) throws UnixException;
       
   455 
       
   456     private static native int poll(int fd1, int fd2) throws UnixException;
       
   457 
       
   458     static {
       
   459         AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   460             public Void run() {
       
   461                 System.loadLibrary("nio");
       
   462                 return null;
       
   463         }});
       
   464         init();
       
   465     }
       
   466 }