1 /* 2 * Copyright (c) 2008, 2009, 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 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(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) { 106 super(dir, 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(dir, 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 }