rev 12972 : 8140606: Update library code to use internal Unsafe
Reviewed-by: duke
1 /*
2 * Copyright (c) 2008, 2013, 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.UnixConstants.*;
36
37 /**
38 * Solaris implementation of WatchService based on file events notification
39 * facility.
40 */
41
42 class SolarisWatchService
43 extends AbstractWatchService
44 {
45 private static final Unsafe unsafe = Unsafe.getUnsafe();
46 private static int addressSize = unsafe.addressSize();
47
48 private static int dependsArch(int value32, int value64) {
49 return (addressSize == 4) ? value32 : value64;
50 }
51
52 /*
53 * typedef struct port_event {
54 * int portev_events;
55 * ushort_t portev_source;
56 * ushort_t portev_pad;
57 * uintptr_t portev_object;
58 * void *portev_user;
59 * } port_event_t;
60 */
61 private static final int SIZEOF_PORT_EVENT = dependsArch(16, 24);
62 private static final int OFFSETOF_EVENTS = 0;
63 private static final int OFFSETOF_SOURCE = 4;
64 private static final int OFFSETOF_OBJECT = 8;
65
66 /*
67 * typedef struct file_obj {
68 * timestruc_t fo_atime;
69 * timestruc_t fo_mtime;
70 * timestruc_t fo_ctime;
71 * uintptr_t fo_pad[3];
72 * char *fo_name;
73 * } file_obj_t;
74 */
75 private static final int SIZEOF_FILEOBJ = dependsArch(40, 80);
76 private static final int OFFSET_FO_NAME = dependsArch(36, 72);
77
78 // port sources
79 private static final short PORT_SOURCE_USER = 3;
80 private static final short PORT_SOURCE_FILE = 7;
81
82 // user-watchable events
83 private static final int FILE_MODIFIED = 0x00000002;
84 private static final int FILE_ATTRIB = 0x00000004;
85 private static final int FILE_NOFOLLOW = 0x10000000;
86
87 // exception events
88 private static final int FILE_DELETE = 0x00000010;
89 private static final int FILE_RENAME_TO = 0x00000020;
90 private static final int FILE_RENAME_FROM = 0x00000040;
91 private static final int UNMOUNTED = 0x20000000;
92 private static final int MOUNTEDOVER = 0x40000000;
93
94 // background thread to read change events
95 private final Poller poller;
96
97 SolarisWatchService(UnixFileSystem fs) throws IOException {
98 int port = -1;
99 try {
100 port = portCreate();
101 } catch (UnixException x) {
102 throw new IOException(x.errorString());
103 }
104
105 this.poller = new Poller(fs, this, port);
106 this.poller.start();
107 }
108
109 @Override
110 WatchKey register(Path dir,
111 WatchEvent.Kind<?>[] events,
112 WatchEvent.Modifier... modifiers)
113 throws IOException
114 {
115 // delegate to poller
116 return poller.register(dir, events, modifiers);
117 }
118
119 @Override
120 void implClose() throws IOException {
121 // delegate to poller
122 poller.close();
123 }
124
125 /**
126 * WatchKey implementation
127 */
128 private class SolarisWatchKey extends AbstractWatchKey
129 implements DirectoryNode
130 {
131 private final UnixFileKey fileKey;
132
133 // pointer to native file_obj object
134 private final long object;
135
136 // events (may be changed). set to null when watch key is invalid
137 private volatile Set<? extends WatchEvent.Kind<?>> events;
138
139 // map of entries in directory; created lazily; accessed only by
140 // poller thread.
141 private Map<Path,EntryNode> children = new HashMap<>();
142
143 SolarisWatchKey(SolarisWatchService watcher,
144 UnixPath dir,
145 UnixFileKey fileKey,
146 long object,
147 Set<? extends WatchEvent.Kind<?>> events)
148 {
149 super(dir, watcher);
150 this.fileKey = fileKey;
151 this.object = object;
152 this.events = events;
153 }
154
155 UnixPath getDirectory() {
156 return (UnixPath)watchable();
157 }
158
159 UnixFileKey getFileKey() {
160 return fileKey;
161 }
162
163 @Override
164 public long object() {
165 return object;
166 }
167
168 void invalidate() {
169 events = null;
170 }
171
172 Set<? extends WatchEvent.Kind<?>> events() {
173 return events;
174 }
175
176 void setEvents(Set<? extends WatchEvent.Kind<?>> events) {
177 this.events = events;
178 }
179
180 Map<Path,EntryNode> children() {
181 return children;
182 }
183
184 @Override
185 public boolean isValid() {
186 return events != null;
187 }
188
189 @Override
190 public void cancel() {
191 if (isValid()) {
192 // delegate to poller
193 poller.cancel(this);
194 }
195 }
196
197 @Override
198 public void addChild(Path name, EntryNode node) {
199 children.put(name, node);
200 }
201
202 @Override
203 public void removeChild(Path name) {
204 children.remove(name);
205 }
206
207 @Override
208 public EntryNode getChild(Path name) {
209 return children.get(name);
210 }
211 }
212
213 /**
214 * Background thread to read from port
215 */
216 private class Poller extends AbstractPoller {
217
218 // maximum number of events to read per call to port_getn
219 private static final int MAX_EVENT_COUNT = 128;
220
221 // events that map to ENTRY_DELETE
222 private static final int FILE_REMOVED =
223 (FILE_DELETE|FILE_RENAME_TO|FILE_RENAME_FROM);
224
225 // events that tell us not to re-associate the object
226 private static final int FILE_EXCEPTION =
227 (FILE_REMOVED|UNMOUNTED|MOUNTEDOVER);
228
229 // address of event buffers (used to receive events with port_getn)
230 private final long bufferAddress;
231
232 private final SolarisWatchService watcher;
233
234 // the I/O port
235 private final int port;
236
237 // maps file key (dev/inode) to WatchKey
238 private final Map<UnixFileKey,SolarisWatchKey> fileKey2WatchKey;
239
240 // maps file_obj object to Node
241 private final Map<Long,Node> object2Node;
242
243 /**
244 * Create a new instance
245 */
246 Poller(UnixFileSystem fs, SolarisWatchService watcher, int port) {
247 this.watcher = watcher;
248 this.port = port;
249 this.bufferAddress =
250 unsafe.allocateMemory(SIZEOF_PORT_EVENT * MAX_EVENT_COUNT);
251 this.fileKey2WatchKey = new HashMap<UnixFileKey,SolarisWatchKey>();
252 this.object2Node = new HashMap<Long,Node>();
253 }
254
255 @Override
256 void wakeup() throws IOException {
257 // write to port to wakeup polling thread
258 try {
259 portSend(port, 0);
260 } catch (UnixException x) {
261 throw new IOException(x.errorString());
262 }
263 }
264
265 @Override
266 Object implRegister(Path obj,
267 Set<? extends WatchEvent.Kind<?>> events,
268 WatchEvent.Modifier... modifiers)
269 {
270 // no modifiers supported at this time
271 if (modifiers.length > 0) {
272 for (WatchEvent.Modifier modifier: modifiers) {
273 if (modifier == null)
274 return new NullPointerException();
275 if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
276 continue; // ignore
277 return new UnsupportedOperationException("Modifier not supported");
278 }
279 }
280
281 UnixPath dir = (UnixPath)obj;
282
283 // check file is directory
284 UnixFileAttributes attrs = null;
285 try {
286 attrs = UnixFileAttributes.get(dir, true);
287 } catch (UnixException x) {
288 return x.asIOException(dir);
289 }
290 if (!attrs.isDirectory()) {
291 return new NotDirectoryException(dir.getPathForExceptionMessage());
292 }
293
294 // if already registered then update the events and return existing key
295 UnixFileKey fileKey = attrs.fileKey();
296 SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey);
297 if (watchKey != null) {
298 try {
299 updateEvents(watchKey, events);
300 } catch (UnixException x) {
301 return x.asIOException(dir);
302 }
303 return watchKey;
304 }
305
306 // register directory
307 long object = 0L;
308 try {
309 object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB));
310 } catch (UnixException x) {
311 return x.asIOException(dir);
312 }
313
314 // create watch key and insert it into maps
315 watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events);
316 object2Node.put(object, watchKey);
317 fileKey2WatchKey.put(fileKey, watchKey);
318
319 // register all entries in directory
320 registerChildren(dir, watchKey, false, false);
321
322 return watchKey;
323 }
324
325 // release resources for single entry
326 void releaseChild(EntryNode node) {
327 long object = node.object();
328 if (object != 0L) {
329 object2Node.remove(object);
330 releaseObject(object, true);
331 node.setObject(0L);
332 }
333 }
334
335 // release resources for entries in directory
336 void releaseChildren(SolarisWatchKey key) {
337 for (EntryNode node: key.children().values()) {
338 releaseChild(node);
339 }
340 }
341
342 // cancel single key
343 @Override
344 void implCancelKey(WatchKey obj) {
345 SolarisWatchKey key = (SolarisWatchKey)obj;
346 if (key.isValid()) {
347 fileKey2WatchKey.remove(key.getFileKey());
348
349 // release resources for entries
350 releaseChildren(key);
351
352 // release resources for directory
353 long object = key.object();
354 object2Node.remove(object);
355 releaseObject(object, true);
356
357 // and finally invalidate the key
358 key.invalidate();
359 }
360 }
361
362 // close watch service
363 @Override
364 void implCloseAll() {
365 // release all native resources
366 for (Long object: object2Node.keySet()) {
367 releaseObject(object, true);
368 }
369
370 // invalidate all keys
371 for (Map.Entry<UnixFileKey,SolarisWatchKey> entry: fileKey2WatchKey.entrySet()) {
372 entry.getValue().invalidate();
373 }
374
375 // clean-up
376 object2Node.clear();
377 fileKey2WatchKey.clear();
378
379 // free global resources
380 unsafe.freeMemory(bufferAddress);
381 UnixNativeDispatcher.close(port);
382 }
383
384 /**
385 * Poller main loop. Blocks on port_getn waiting for events and then
386 * processes them.
387 */
388 @Override
389 public void run() {
390 try {
391 for (;;) {
392 int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT);
393 assert n > 0;
394
395 long address = bufferAddress;
396 for (int i=0; i<n; i++) {
397 boolean shutdown = processEvent(address);
398 if (shutdown)
399 return;
400 address += SIZEOF_PORT_EVENT;
401 }
402 }
403 } catch (UnixException x) {
404 x.printStackTrace();
405 }
406 }
407
408 /**
409 * Process a single port_event
410 *
411 * Returns true if poller thread is requested to shutdown.
412 */
413 boolean processEvent(long address) {
414 // pe->portev_source
415 short source = unsafe.getShort(address + OFFSETOF_SOURCE);
416 // pe->portev_object
417 long object = unsafe.getAddress(address + OFFSETOF_OBJECT);
418 // pe->portev_events
419 int events = unsafe.getInt(address + OFFSETOF_EVENTS);
420
421 // user event is trigger to process pending requests
422 if (source != PORT_SOURCE_FILE) {
423 if (source == PORT_SOURCE_USER) {
424 // process any pending requests
425 boolean shutdown = processRequests();
426 if (shutdown)
427 return true;
428 }
429 return false;
430 }
431
432 // lookup object to get Node
433 Node node = object2Node.get(object);
434 if (node == null) {
435 // should not happen
436 return false;
437 }
438
439 // As a workaround for 6642290 and 6636438/6636412 we don't use
440 // FILE_EXCEPTION events to tell use not to register the file.
441 // boolean reregister = (events & FILE_EXCEPTION) == 0;
442 boolean reregister = true;
443
444 // If node is EntryNode then event relates to entry in directory
445 // If node is a SolarisWatchKey (DirectoryNode) then event relates
446 // to a watched directory.
447 boolean isDirectory = (node instanceof SolarisWatchKey);
448 if (isDirectory) {
449 processDirectoryEvents((SolarisWatchKey)node, events);
450 } else {
451 boolean ignore = processEntryEvents((EntryNode)node, events);
452 if (ignore)
453 reregister = false;
454 }
455
456 // need to re-associate to get further events
457 if (reregister) {
458 try {
459 events = FILE_MODIFIED | FILE_ATTRIB;
460 if (!isDirectory) events |= FILE_NOFOLLOW;
461 portAssociate(port,
462 PORT_SOURCE_FILE,
463 object,
464 events);
465 } catch (UnixException x) {
466 // unable to re-register
467 reregister = false;
468 }
469 }
470
471 // object is not re-registered so release resources. If
472 // object is a watched directory then signal key
473 if (!reregister) {
474 // release resources
475 object2Node.remove(object);
476 releaseObject(object, false);
477
478 // if watch key then signal it
479 if (isDirectory) {
480 SolarisWatchKey key = (SolarisWatchKey)node;
481 fileKey2WatchKey.remove( key.getFileKey() );
482 key.invalidate();
483 key.signal();
484 } else {
485 // if entry then remove it from parent
486 EntryNode entry = (EntryNode)node;
487 SolarisWatchKey key = (SolarisWatchKey)entry.parent();
488 key.removeChild(entry.name());
489 }
490 }
491
492 return false;
493 }
494
495 /**
496 * Process directory events. If directory is modified then re-scan
497 * directory to register any new entries
498 */
499 void processDirectoryEvents(SolarisWatchKey key, int mask) {
500 if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) {
501 registerChildren(key.getDirectory(), key,
502 key.events().contains(StandardWatchEventKinds.ENTRY_CREATE),
503 key.events().contains(StandardWatchEventKinds.ENTRY_DELETE));
504 }
505 }
506
507 /**
508 * Process events for entries in registered directories. Returns {@code
509 * true} if events are ignored because the watch key has been cancelled.
510 */
511 boolean processEntryEvents(EntryNode node, int mask) {
512 SolarisWatchKey key = (SolarisWatchKey)node.parent();
513 Set<? extends WatchEvent.Kind<?>> events = key.events();
514 if (events == null) {
515 // key has been cancelled so ignore event
516 return true;
517 }
518
519 // entry modified
520 if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) &&
521 events.contains(StandardWatchEventKinds.ENTRY_MODIFY))
522 {
523 key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name());
524 }
525
526
527 return false;
528 }
529
530 /**
531 * Registers all entries in the given directory
532 *
533 * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters
534 * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued
535 * when new entries are found. When initially registering a directory
536 * they will always be false. When re-scanning a directory then it
537 * depends on if the events are enabled or not.
538 */
539 void registerChildren(UnixPath dir,
540 SolarisWatchKey parent,
541 boolean sendCreateEvents,
542 boolean sendDeleteEvents)
543 {
544 boolean isModifyEnabled =
545 parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ;
546
547 // reset visited flag on entries so that we can detect file deletes
548 for (EntryNode node: parent.children().values()) {
549 node.setVisited(false);
550 }
551
552 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
553 for (Path entry: stream) {
554 Path name = entry.getFileName();
555
556 // skip entry if already registered
557 EntryNode node = parent.getChild(name);
558 if (node != null) {
559 node.setVisited(true);
560 continue;
561 }
562
563 // new entry found
564
565 long object = 0L;
566 int errno = 0;
567 boolean addNode = false;
568
569 // if ENTRY_MODIFY enabled then we register the entry for events
570 if (isModifyEnabled) {
571 try {
572 UnixPath path = (UnixPath)entry;
573 int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
574 object = registerImpl(path, events);
575 addNode = true;
576 } catch (UnixException x) {
577 errno = x.errno();
578 }
579 } else {
580 addNode = true;
581 }
582
583 if (addNode) {
584 // create node
585 node = new EntryNode(object, (UnixPath)entry.getFileName(), parent);
586 node.setVisited(true);
587 // tell the parent about it
588 parent.addChild(entry.getFileName(), node);
589 if (object != 0L)
590 object2Node.put(object, node);
591 }
592
593 // send ENTRY_CREATE event for the new file
594 // send ENTRY_DELETE event for files that were deleted immediately
595 boolean deleted = (errno == ENOENT);
596 if (sendCreateEvents && (addNode || deleted))
597 parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);
598 if (sendDeleteEvents && deleted)
599 parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
600
601 }
602 } catch (DirectoryIteratorException | IOException x) {
603 // queue OVERFLOW event so that user knows to re-scan directory
604 parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
605 return;
606 }
607
608 // clean-up and send ENTRY_DELETE events for any entries that were
609 // not found
610 Iterator<Map.Entry<Path,EntryNode>> iterator =
611 parent.children().entrySet().iterator();
612 while (iterator.hasNext()) {
613 Map.Entry<Path,EntryNode> entry = iterator.next();
614 EntryNode node = entry.getValue();
615 if (!node.isVisited()) {
616 long object = node.object();
617 if (object != 0L) {
618 object2Node.remove(object);
619 releaseObject(object, true);
620 }
621 if (sendDeleteEvents)
622 parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name());
623 iterator.remove();
624 }
625 }
626 }
627
628 /**
629 * Update watch key's events. If ENTRY_MODIFY changes to be enabled
630 * then register each file in the directory; If ENTRY_MODIFY changed to
631 * be disabled then unregister each file.
632 */
633 void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events)
634 throws UnixException
635 {
636
637 // update events, remembering if ENTRY_MODIFY was previously
638 // enabled or disabled.
639 boolean oldModifyEnabled = key.events()
640 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
641 key.setEvents(events);
642
643 // check if ENTRY_MODIFY has changed
644 boolean newModifyEnabled = events
645 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
646 if (newModifyEnabled != oldModifyEnabled) {
647 UnixException ex = null;
648 for (EntryNode node: key.children().values()) {
649 if (newModifyEnabled) {
650 // register
651 UnixPath path = key.getDirectory().resolve(node.name());
652 int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
653 try {
654 long object = registerImpl(path, ev);
655 object2Node.put(object, node);
656 node.setObject(object);
657 } catch (UnixException x) {
658 // if file has been deleted then it will be detected
659 // as a FILE_MODIFIED event on the directory
660 if (x.errno() != ENOENT) {
661 ex = x;
662 break;
663 }
664 }
665 } else {
666 // unregister
667 releaseChild(node);
668 }
669 }
670
671 // an error occurred
672 if (ex != null) {
673 releaseChildren(key);
674 throw ex;
675 }
676 }
677 }
678
679 /**
680 * Calls port_associate to register the given path.
681 * Returns pointer to fileobj structure that is allocated for
682 * the registration.
683 */
684 long registerImpl(UnixPath dir, int events)
685 throws UnixException
686 {
687 // allocate memory for the path (file_obj->fo_name field)
688 byte[] path = dir.getByteArrayForSysCalls();
689 int len = path.length;
690 long name = unsafe.allocateMemory(len+1);
691 unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null,
692 name, (long)len);
693 unsafe.putByte(name + len, (byte)0);
694
695 // allocate memory for filedatanode structure - this is the object
696 // to port_associate
697 long object = unsafe.allocateMemory(SIZEOF_FILEOBJ);
698 unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0);
699 unsafe.putAddress(object + OFFSET_FO_NAME, name);
700
701 // associate the object with the port
702 try {
703 portAssociate(port,
704 PORT_SOURCE_FILE,
705 object,
706 events);
707 } catch (UnixException x) {
708 // debugging
709 if (x.errno() == EAGAIN) {
710 System.err.println("The maximum number of objects associated "+
711 "with the port has been reached");
712 }
713
714 unsafe.freeMemory(name);
715 unsafe.freeMemory(object);
716 throw x;
717 }
718 return object;
719 }
720
721 /**
722 * Frees all resources for an file_obj object; optionally remove
723 * association from port
724 */
725 void releaseObject(long object, boolean dissociate) {
726 // remove association
727 if (dissociate) {
728 try {
729 portDissociate(port, PORT_SOURCE_FILE, object);
730 } catch (UnixException x) {
731 // ignore
732 }
733 }
734
735 // free native memory
736 long name = unsafe.getAddress(object + OFFSET_FO_NAME);
737 unsafe.freeMemory(name);
738 unsafe.freeMemory(object);
739 }
740 }
741
742 /**
743 * A node with native (file_obj) resources
744 */
745 private static interface Node {
746 long object();
747 }
748
749 /**
750 * A directory node with a map of the entries in the directory
751 */
752 private static interface DirectoryNode extends Node {
753 void addChild(Path name, EntryNode node);
754 void removeChild(Path name);
755 EntryNode getChild(Path name);
756 }
757
758 /**
759 * An implementation of a node that is an entry in a directory.
760 */
761 private static class EntryNode implements Node {
762 private long object;
763 private final UnixPath name;
764 private final DirectoryNode parent;
765 private boolean visited;
766
767 EntryNode(long object, UnixPath name, DirectoryNode parent) {
768 this.object = object;
769 this.name = name;
770 this.parent = parent;
771 }
772
773 @Override
774 public long object() {
775 return object;
776 }
777
778 void setObject(long ptr) {
779 this.object = ptr;
780 }
781
782 UnixPath name() {
783 return name;
784 }
785
786 DirectoryNode parent() {
787 return parent;
788 }
789
790 boolean isVisited() {
791 return visited;
792 }
793
794 void setVisited(boolean v) {
795 this.visited = v;
796 }
797 }
798
799 // -- native methods --
800
801 private static native void init();
802
803 private static native int portCreate() throws UnixException;
804
805 private static native void portAssociate(int port, int source, long object, int events)
806 throws UnixException;
807
808 private static native void portDissociate(int port, int source, long object)
809 throws UnixException;
810
811 private static native void portSend(int port, int events)
812 throws UnixException;
813
814 private static native int portGetn(int port, long address, int max)
815 throws UnixException;
816
817 static {
818 AccessController.doPrivileged(new PrivilegedAction<Void>() {
819 public Void run() {
820 System.loadLibrary("nio");
821 return null;
822 }});
823 init();
824 }
825 }
--- EOF ---