• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.io.IOException;
29 import java.nio.file.NotDirectoryException;
30 import java.nio.file.Path;
31 import java.nio.file.StandardWatchEventKinds;
32 import java.nio.file.WatchEvent;
33 import java.nio.file.WatchKey;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.Set;
37 
38 import dalvik.system.CloseGuard;
39 import sun.misc.Unsafe;
40 
41 import static sun.nio.fs.UnixConstants.EAGAIN;
42 import static sun.nio.fs.UnixConstants.EMFILE;
43 import static sun.nio.fs.UnixConstants.ENOSPC;
44 import static sun.nio.fs.UnixNativeDispatcher.read;
45 import static sun.nio.fs.UnixNativeDispatcher.write;
46 
47 /**
48  * Linux implementation of WatchService based on inotify.
49  *
50  * In summary a background thread polls inotify plus a socket used for the wakeup
51  * mechanism. Requests to add or remove a watch, or close the watch service,
52  * cause the thread to wakeup and process the request. Events are processed
53  * by the thread which causes it to signal/queue the corresponding watch keys.
54  */
55 
56 class LinuxWatchService
57     extends AbstractWatchService
58 {
59     private static final Unsafe unsafe = Unsafe.getUnsafe();
60 
61     // background thread to read change events
62     private final Poller poller;
63 
LinuxWatchService(UnixFileSystem fs)64     LinuxWatchService(UnixFileSystem fs) throws IOException {
65         // initialize inotify
66         int ifd = - 1;
67         try {
68             ifd = inotifyInit();
69         } catch (UnixException x) {
70             String msg = (x.errno() == EMFILE) ?
71                 "User limit of inotify instances reached or too many open files" :
72                 x.errorString();
73             throw new IOException(msg);
74         }
75 
76         // configure inotify to be non-blocking
77         // create socketpair used in the close mechanism
78         int sp[] = new int[2];
79         try {
80             configureBlocking(ifd, false);
81             socketpair(sp);
82             configureBlocking(sp[0], false);
83         } catch (UnixException x) {
84             UnixNativeDispatcher.close(ifd);
85             throw new IOException(x.errorString());
86         }
87 
88         this.poller = new Poller(fs, this, ifd, sp);
89         this.poller.start();
90     }
91 
92     @Override
register(Path dir, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)93     WatchKey register(Path dir,
94                       WatchEvent.Kind<?>[] events,
95                       WatchEvent.Modifier... modifiers)
96          throws IOException
97     {
98         // delegate to poller
99         return poller.register(dir, events, modifiers);
100     }
101 
102     @Override
implClose()103     void implClose() throws IOException {
104         // delegate to poller
105         poller.close();
106     }
107 
108     /**
109      * WatchKey implementation
110      */
111     private static class LinuxWatchKey extends AbstractWatchKey {
112         // inotify descriptor
113         private final int ifd;
114         // watch descriptor
115         private volatile int wd;
116 
LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd)117         LinuxWatchKey(UnixPath dir, LinuxWatchService watcher, int ifd, int wd) {
118             super(dir, watcher);
119             this.ifd = ifd;
120             this.wd = wd;
121         }
122 
descriptor()123         int descriptor() {
124             return wd;
125         }
126 
invalidate(boolean remove)127         void invalidate(boolean remove) {
128             if (remove) {
129                 try {
130                     inotifyRmWatch(ifd, wd);
131                 } catch (UnixException x) {
132                     // ignore
133                 }
134             }
135             wd = -1;
136         }
137 
138         @Override
isValid()139         public boolean isValid() {
140             return (wd != -1);
141         }
142 
143         @Override
cancel()144         public void cancel() {
145             if (isValid()) {
146                 // delegate to poller
147                 ((LinuxWatchService)watcher()).poller.cancel(this);
148             }
149         }
150     }
151 
152     /**
153      * Background thread to read from inotify
154      */
155     private static class Poller extends AbstractPoller {
156         /**
157          * struct inotify_event {
158          *     int          wd;
159          *     uint32_t     mask;
160          *     uint32_t     len;
161          *     char name    __flexarr;  // present if len > 0
162          * } act_t;
163          */
164         private static final int SIZEOF_INOTIFY_EVENT  = eventSize();
165         private static final int[] offsets             = eventOffsets();
166         private static final int OFFSETOF_WD           = offsets[0];
167         private static final int OFFSETOF_MASK         = offsets[1];
168         private static final int OFFSETOF_LEN          = offsets[3];
169         private static final int OFFSETOF_NAME         = offsets[4];
170 
171         private static final int IN_MODIFY          = 0x00000002;
172         private static final int IN_ATTRIB          = 0x00000004;
173         private static final int IN_MOVED_FROM      = 0x00000040;
174         private static final int IN_MOVED_TO        = 0x00000080;
175         private static final int IN_CREATE          = 0x00000100;
176         private static final int IN_DELETE          = 0x00000200;
177 
178         private static final int IN_UNMOUNT         = 0x00002000;
179         private static final int IN_Q_OVERFLOW      = 0x00004000;
180         private static final int IN_IGNORED         = 0x00008000;
181 
182         // sizeof buffer for when polling inotify
183         private static final int BUFFER_SIZE = 8192;
184 
185         private final UnixFileSystem fs;
186         private final LinuxWatchService watcher;
187 
188         // inotify file descriptor
189         private final int ifd;
190         // socketpair used to shutdown polling thread
191         private final int socketpair[];
192         // maps watch descriptor to Key
193         private final Map<Integer,LinuxWatchKey> wdToKey;
194         // address of read buffer
195         private final long address;
196 
197         // Android-changed: Add CloseGuard support.
198         private final CloseGuard guard = CloseGuard.get();
199 
Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp)200         Poller(UnixFileSystem fs, LinuxWatchService watcher, int ifd, int[] sp) {
201             this.fs = fs;
202             this.watcher = watcher;
203             this.ifd = ifd;
204             this.socketpair = sp;
205             this.wdToKey = new HashMap<Integer,LinuxWatchKey>();
206             this.address = unsafe.allocateMemory(BUFFER_SIZE);
207             // Android-changed: Add CloseGuard support.
208             guard.open("close");
209         }
210 
211         @Override
wakeup()212         void wakeup() throws IOException {
213             // write to socketpair to wakeup polling thread
214             try {
215                 write(socketpair[1], address, 1);
216             } catch (UnixException x) {
217                 throw new IOException(x.errorString());
218             }
219         }
220 
221         @Override
implRegister(Path obj, Set<? extends WatchEvent.Kind<?>> events, WatchEvent.Modifier... modifiers)222         Object implRegister(Path obj,
223                             Set<? extends WatchEvent.Kind<?>> events,
224                             WatchEvent.Modifier... modifiers)
225         {
226             UnixPath dir = (UnixPath)obj;
227 
228             int mask = 0;
229             for (WatchEvent.Kind<?> event: events) {
230                 if (event == StandardWatchEventKinds.ENTRY_CREATE) {
231                     mask |= IN_CREATE | IN_MOVED_TO;
232                     continue;
233                 }
234                 if (event == StandardWatchEventKinds.ENTRY_DELETE) {
235                     mask |= IN_DELETE | IN_MOVED_FROM;
236                     continue;
237                 }
238                 if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
239                     mask |= IN_MODIFY | IN_ATTRIB;
240                     continue;
241                 }
242             }
243 
244             // no modifiers supported at this time
245             if (modifiers.length > 0) {
246                 for (WatchEvent.Modifier modifier: modifiers) {
247                     if (modifier == null)
248                         return new NullPointerException();
249                     if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
250                         continue; // ignore
251                     return new UnsupportedOperationException("Modifier not supported");
252                 }
253             }
254 
255             // check file is directory
256             UnixFileAttributes attrs = null;
257             try {
258                 attrs = UnixFileAttributes.get(dir, true);
259             } catch (UnixException x) {
260                 return x.asIOException(dir);
261             }
262             if (!attrs.isDirectory()) {
263                 return new NotDirectoryException(dir.getPathForExceptionMessage());
264             }
265 
266             // register with inotify (replaces existing mask if already registered)
267             int wd = -1;
268             try {
269                 NativeBuffer buffer =
270                     NativeBuffers.asNativeBuffer(dir.getByteArrayForSysCalls());
271                 try {
272                     wd = inotifyAddWatch(ifd, buffer.address(), mask);
273                 } finally {
274                     buffer.release();
275                 }
276             } catch (UnixException x) {
277                 if (x.errno() == ENOSPC) {
278                     return new IOException("User limit of inotify watches reached");
279                 }
280                 return x.asIOException(dir);
281             }
282 
283             // ensure watch descriptor is in map
284             LinuxWatchKey key = wdToKey.get(wd);
285             if (key == null) {
286                 key = new LinuxWatchKey(dir, watcher, ifd, wd);
287                 wdToKey.put(wd, key);
288             }
289             return key;
290         }
291 
292         // cancel single key
293         @Override
implCancelKey(WatchKey obj)294         void implCancelKey(WatchKey obj) {
295             LinuxWatchKey key = (LinuxWatchKey)obj;
296             if (key.isValid()) {
297                 wdToKey.remove(key.descriptor());
298                 key.invalidate(true);
299             }
300         }
301 
302         // close watch service
303         @Override
implCloseAll()304         void implCloseAll() {
305             // Android-changed: Add CloseGuard support.
306             guard.close();
307             // invalidate all keys
308             for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
309                 entry.getValue().invalidate(true);
310             }
311             wdToKey.clear();
312 
313             // free resources
314             unsafe.freeMemory(address);
315             UnixNativeDispatcher.close(socketpair[0]);
316             UnixNativeDispatcher.close(socketpair[1]);
317             UnixNativeDispatcher.close(ifd);
318         }
319 
finalize()320         protected void finalize() throws Throwable {
321             try {
322                 if (guard != null) {
323                     guard.warnIfOpen();
324                 }
325                 close();
326             } finally {
327                 super.finalize();
328             }
329         }
330 
331         /**
332          * Poller main loop
333          */
334         @Override
run()335         public void run() {
336             try {
337                 for (;;) {
338                     int nReady, bytesRead;
339 
340                     // wait for close or inotify event
341                     nReady = poll(ifd, socketpair[0]);
342 
343                     // read from inotify
344                     try {
345                         bytesRead = read(ifd, address, BUFFER_SIZE);
346                     } catch (UnixException x) {
347                         if (x.errno() != EAGAIN)
348                             throw x;
349                         bytesRead = 0;
350                     }
351 
352                     // process any pending requests
353                     if ((nReady > 1) || (nReady == 1 && bytesRead == 0)) {
354                         try {
355                             read(socketpair[0], address, BUFFER_SIZE);
356                             boolean shutdown = processRequests();
357                             if (shutdown)
358                                 break;
359                         } catch (UnixException x) {
360                             if (x.errno() != UnixConstants.EAGAIN)
361                                 throw x;
362                         }
363                     }
364 
365                     // iterate over buffer to decode events
366                     int offset = 0;
367                     while (offset < bytesRead) {
368                         long event = address + offset;
369                         int wd = unsafe.getInt(event + OFFSETOF_WD);
370                         int mask = unsafe.getInt(event + OFFSETOF_MASK);
371                         int len = unsafe.getInt(event + OFFSETOF_LEN);
372 
373                         // file name
374                         UnixPath name = null;
375                         if (len > 0) {
376                             int actual = len;
377 
378                             // null-terminated and maybe additional null bytes to
379                             // align the next event
380                             while (actual > 0) {
381                                 long last = event + OFFSETOF_NAME + actual - 1;
382                                 if (unsafe.getByte(last) != 0)
383                                     break;
384                                 actual--;
385                             }
386                             if (actual > 0) {
387                                 byte[] buf = new byte[actual];
388                                 // unsafe.copyMemory(null, event + OFFSETOF_NAME,
389                                 //    buf, Unsafe.ARRAY_BYTE_BASE_OFFSET, actual);
390 
391                                 // Android-changed: We don't have Unsafe.copyMemory yet, so we use
392                                 // getByte.
393                                 for(int i = 0; i < actual; i++) {
394                                     buf[i] = unsafe.getByte(event + OFFSETOF_NAME + i);
395                                 }
396                                 name = new UnixPath(fs, buf);
397                             }
398                         }
399 
400                         // process event
401                         processEvent(wd, mask, name);
402 
403                         offset += (SIZEOF_INOTIFY_EVENT + len);
404                     }
405                 }
406             } catch (UnixException x) {
407                 x.printStackTrace();
408             }
409         }
410 
411 
412         /**
413          * map inotify event to WatchEvent.Kind
414          */
maskToEventKind(int mask)415         private WatchEvent.Kind<?> maskToEventKind(int mask) {
416             if ((mask & IN_MODIFY) > 0)
417                 return StandardWatchEventKinds.ENTRY_MODIFY;
418             if ((mask & IN_ATTRIB) > 0)
419                 return StandardWatchEventKinds.ENTRY_MODIFY;
420             if ((mask & IN_CREATE) > 0)
421                 return StandardWatchEventKinds.ENTRY_CREATE;
422             if ((mask & IN_MOVED_TO) > 0)
423                 return StandardWatchEventKinds.ENTRY_CREATE;
424             if ((mask & IN_DELETE) > 0)
425                 return StandardWatchEventKinds.ENTRY_DELETE;
426             if ((mask & IN_MOVED_FROM) > 0)
427                 return StandardWatchEventKinds.ENTRY_DELETE;
428             return null;
429         }
430 
431         /**
432          * Process event from inotify
433          */
processEvent(int wd, int mask, final UnixPath name)434         private void processEvent(int wd, int mask, final UnixPath name) {
435             // overflow - signal all keys
436             if ((mask & IN_Q_OVERFLOW) > 0) {
437                 for (Map.Entry<Integer,LinuxWatchKey> entry: wdToKey.entrySet()) {
438                     entry.getValue()
439                         .signalEvent(StandardWatchEventKinds.OVERFLOW, null);
440                 }
441                 return;
442             }
443 
444             // lookup wd to get key
445             LinuxWatchKey key = wdToKey.get(wd);
446             if (key == null)
447                 return; // should not happen
448 
449             // file deleted
450             if ((mask & IN_IGNORED) > 0) {
451                 wdToKey.remove(wd);
452                 key.invalidate(false);
453                 key.signal();
454                 return;
455             }
456 
457             // event for directory itself
458             if (name == null)
459                 return;
460 
461             // map to event and queue to key
462             WatchEvent.Kind<?> kind = maskToEventKind(mask);
463             if (kind != null) {
464                 key.signalEvent(kind, name);
465             }
466         }
467     }
468 
469     // -- native methods --
470 
471     // sizeof inotify_event
eventSize()472     private static native int eventSize();
473 
474     // offsets of inotify_event
eventOffsets()475     private static native int[] eventOffsets();
476 
inotifyInit()477     private static native int inotifyInit() throws UnixException;
478 
inotifyAddWatch(int fd, long pathAddress, int mask)479     private static native int inotifyAddWatch(int fd, long pathAddress, int mask)
480         throws UnixException;
481 
inotifyRmWatch(int fd, int wd)482     private static native void inotifyRmWatch(int fd, int wd)
483         throws UnixException;
484 
configureBlocking(int fd, boolean blocking)485     private static native void configureBlocking(int fd, boolean blocking)
486         throws UnixException;
487 
socketpair(int[] sv)488     private static native void socketpair(int[] sv) throws UnixException;
489 
poll(int fd1, int fd2)490     private static native int poll(int fd1, int fd2) throws UnixException;
491 }
492