|
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 } |