Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java
32288 views
/*1* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.nio.fs;2627import java.nio.file.*;28import java.security.AccessController;29import java.security.PrivilegedAction;30import java.util.*;31import java.io.IOException;32import sun.misc.Unsafe;3334import static sun.nio.fs.UnixConstants.*;3536/**37* Solaris implementation of WatchService based on file events notification38* facility.39*/4041class SolarisWatchService42extends AbstractWatchService43{44private static final Unsafe unsafe = Unsafe.getUnsafe();45private static int addressSize = unsafe.addressSize();4647private static int dependsArch(int value32, int value64) {48return (addressSize == 4) ? value32 : value64;49}5051/*52* typedef struct port_event {53* int portev_events;54* ushort_t portev_source;55* ushort_t portev_pad;56* uintptr_t portev_object;57* void *portev_user;58* } port_event_t;59*/60private static final int SIZEOF_PORT_EVENT = dependsArch(16, 24);61private static final int OFFSETOF_EVENTS = 0;62private static final int OFFSETOF_SOURCE = 4;63private static final int OFFSETOF_OBJECT = 8;6465/*66* typedef struct file_obj {67* timestruc_t fo_atime;68* timestruc_t fo_mtime;69* timestruc_t fo_ctime;70* uintptr_t fo_pad[3];71* char *fo_name;72* } file_obj_t;73*/74private static final int SIZEOF_FILEOBJ = dependsArch(40, 80);75private static final int OFFSET_FO_NAME = dependsArch(36, 72);7677// port sources78private static final short PORT_SOURCE_USER = 3;79private static final short PORT_SOURCE_FILE = 7;8081// user-watchable events82private static final int FILE_MODIFIED = 0x00000002;83private static final int FILE_ATTRIB = 0x00000004;84private static final int FILE_NOFOLLOW = 0x10000000;8586// exception events87private static final int FILE_DELETE = 0x00000010;88private static final int FILE_RENAME_TO = 0x00000020;89private static final int FILE_RENAME_FROM = 0x00000040;90private static final int UNMOUNTED = 0x20000000;91private static final int MOUNTEDOVER = 0x40000000;9293// background thread to read change events94private final Poller poller;9596SolarisWatchService(UnixFileSystem fs) throws IOException {97int port = -1;98try {99port = portCreate();100} catch (UnixException x) {101throw new IOException(x.errorString());102}103104this.poller = new Poller(fs, this, port);105this.poller.start();106}107108@Override109WatchKey register(Path dir,110WatchEvent.Kind<?>[] events,111WatchEvent.Modifier... modifiers)112throws IOException113{114// delegate to poller115return poller.register(dir, events, modifiers);116}117118@Override119void implClose() throws IOException {120// delegate to poller121poller.close();122}123124/**125* WatchKey implementation126*/127private class SolarisWatchKey extends AbstractWatchKey128implements DirectoryNode129{130private final UnixFileKey fileKey;131132// pointer to native file_obj object133private final long object;134135// events (may be changed). set to null when watch key is invalid136private volatile Set<? extends WatchEvent.Kind<?>> events;137138// map of entries in directory; created lazily; accessed only by139// poller thread.140private Map<Path,EntryNode> children = new HashMap<>();141142SolarisWatchKey(SolarisWatchService watcher,143UnixPath dir,144UnixFileKey fileKey,145long object,146Set<? extends WatchEvent.Kind<?>> events)147{148super(dir, watcher);149this.fileKey = fileKey;150this.object = object;151this.events = events;152}153154UnixPath getDirectory() {155return (UnixPath)watchable();156}157158UnixFileKey getFileKey() {159return fileKey;160}161162@Override163public long object() {164return object;165}166167void invalidate() {168events = null;169}170171Set<? extends WatchEvent.Kind<?>> events() {172return events;173}174175void setEvents(Set<? extends WatchEvent.Kind<?>> events) {176this.events = events;177}178179Map<Path,EntryNode> children() {180return children;181}182183@Override184public boolean isValid() {185return events != null;186}187188@Override189public void cancel() {190if (isValid()) {191// delegate to poller192poller.cancel(this);193}194}195196@Override197public void addChild(Path name, EntryNode node) {198children.put(name, node);199}200201@Override202public void removeChild(Path name) {203children.remove(name);204}205206@Override207public EntryNode getChild(Path name) {208return children.get(name);209}210}211212/**213* Background thread to read from port214*/215private class Poller extends AbstractPoller {216217// maximum number of events to read per call to port_getn218private static final int MAX_EVENT_COUNT = 128;219220// events that map to ENTRY_DELETE221private static final int FILE_REMOVED =222(FILE_DELETE|FILE_RENAME_TO|FILE_RENAME_FROM);223224// events that tell us not to re-associate the object225private static final int FILE_EXCEPTION =226(FILE_REMOVED|UNMOUNTED|MOUNTEDOVER);227228// address of event buffers (used to receive events with port_getn)229private final long bufferAddress;230231private final SolarisWatchService watcher;232233// the I/O port234private final int port;235236// maps file key (dev/inode) to WatchKey237private final Map<UnixFileKey,SolarisWatchKey> fileKey2WatchKey;238239// maps file_obj object to Node240private final Map<Long,Node> object2Node;241242/**243* Create a new instance244*/245Poller(UnixFileSystem fs, SolarisWatchService watcher, int port) {246this.watcher = watcher;247this.port = port;248this.bufferAddress =249unsafe.allocateMemory(SIZEOF_PORT_EVENT * MAX_EVENT_COUNT);250this.fileKey2WatchKey = new HashMap<UnixFileKey,SolarisWatchKey>();251this.object2Node = new HashMap<Long,Node>();252}253254@Override255void wakeup() throws IOException {256// write to port to wakeup polling thread257try {258portSend(port, 0);259} catch (UnixException x) {260throw new IOException(x.errorString());261}262}263264@Override265Object implRegister(Path obj,266Set<? extends WatchEvent.Kind<?>> events,267WatchEvent.Modifier... modifiers)268{269// no modifiers supported at this time270if (modifiers.length > 0) {271for (WatchEvent.Modifier modifier: modifiers) {272if (modifier == null)273return new NullPointerException();274if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)275continue; // ignore276return new UnsupportedOperationException("Modifier not supported");277}278}279280UnixPath dir = (UnixPath)obj;281282// check file is directory283UnixFileAttributes attrs = null;284try {285attrs = UnixFileAttributes.get(dir, true);286} catch (UnixException x) {287return x.asIOException(dir);288}289if (!attrs.isDirectory()) {290return new NotDirectoryException(dir.getPathForExceptionMessage());291}292293// if already registered then update the events and return existing key294UnixFileKey fileKey = attrs.fileKey();295SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey);296if (watchKey != null) {297try {298updateEvents(watchKey, events);299} catch (UnixException x) {300return x.asIOException(dir);301}302return watchKey;303}304305// register directory306long object = 0L;307try {308object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB));309} catch (UnixException x) {310return x.asIOException(dir);311}312313// create watch key and insert it into maps314watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events);315object2Node.put(object, watchKey);316fileKey2WatchKey.put(fileKey, watchKey);317318// register all entries in directory319registerChildren(dir, watchKey, false, false);320321return watchKey;322}323324// release resources for single entry325void releaseChild(EntryNode node) {326long object = node.object();327if (object != 0L) {328object2Node.remove(object);329releaseObject(object, true);330node.setObject(0L);331}332}333334// release resources for entries in directory335void releaseChildren(SolarisWatchKey key) {336for (EntryNode node: key.children().values()) {337releaseChild(node);338}339}340341// cancel single key342@Override343void implCancelKey(WatchKey obj) {344SolarisWatchKey key = (SolarisWatchKey)obj;345if (key.isValid()) {346fileKey2WatchKey.remove(key.getFileKey());347348// release resources for entries349releaseChildren(key);350351// release resources for directory352long object = key.object();353object2Node.remove(object);354releaseObject(object, true);355356// and finally invalidate the key357key.invalidate();358}359}360361// close watch service362@Override363void implCloseAll() {364// release all native resources365for (Long object: object2Node.keySet()) {366releaseObject(object, true);367}368369// invalidate all keys370for (Map.Entry<UnixFileKey,SolarisWatchKey> entry: fileKey2WatchKey.entrySet()) {371entry.getValue().invalidate();372}373374// clean-up375object2Node.clear();376fileKey2WatchKey.clear();377378// free global resources379unsafe.freeMemory(bufferAddress);380UnixNativeDispatcher.close(port);381}382383/**384* Poller main loop. Blocks on port_getn waiting for events and then385* processes them.386*/387@Override388public void run() {389try {390for (;;) {391int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT);392assert n > 0;393394long address = bufferAddress;395for (int i=0; i<n; i++) {396boolean shutdown = processEvent(address);397if (shutdown)398return;399address += SIZEOF_PORT_EVENT;400}401}402} catch (UnixException x) {403x.printStackTrace();404}405}406407/**408* Process a single port_event409*410* Returns true if poller thread is requested to shutdown.411*/412boolean processEvent(long address) {413// pe->portev_source414short source = unsafe.getShort(address + OFFSETOF_SOURCE);415// pe->portev_object416long object = unsafe.getAddress(address + OFFSETOF_OBJECT);417// pe->portev_events418int events = unsafe.getInt(address + OFFSETOF_EVENTS);419420// user event is trigger to process pending requests421if (source != PORT_SOURCE_FILE) {422if (source == PORT_SOURCE_USER) {423// process any pending requests424boolean shutdown = processRequests();425if (shutdown)426return true;427}428return false;429}430431// lookup object to get Node432Node node = object2Node.get(object);433if (node == null) {434// should not happen435return false;436}437438// As a workaround for 6642290 and 6636438/6636412 we don't use439// FILE_EXCEPTION events to tell use not to register the file.440// boolean reregister = (events & FILE_EXCEPTION) == 0;441boolean reregister = true;442443// If node is EntryNode then event relates to entry in directory444// If node is a SolarisWatchKey (DirectoryNode) then event relates445// to a watched directory.446boolean isDirectory = (node instanceof SolarisWatchKey);447if (isDirectory) {448processDirectoryEvents((SolarisWatchKey)node, events);449} else {450boolean ignore = processEntryEvents((EntryNode)node, events);451if (ignore)452reregister = false;453}454455// need to re-associate to get further events456if (reregister) {457try {458events = FILE_MODIFIED | FILE_ATTRIB;459if (!isDirectory) events |= FILE_NOFOLLOW;460portAssociate(port,461PORT_SOURCE_FILE,462object,463events);464} catch (UnixException x) {465// unable to re-register466reregister = false;467}468}469470// object is not re-registered so release resources. If471// object is a watched directory then signal key472if (!reregister) {473// release resources474object2Node.remove(object);475releaseObject(object, false);476477// if watch key then signal it478if (isDirectory) {479SolarisWatchKey key = (SolarisWatchKey)node;480fileKey2WatchKey.remove( key.getFileKey() );481key.invalidate();482key.signal();483} else {484// if entry then remove it from parent485EntryNode entry = (EntryNode)node;486SolarisWatchKey key = (SolarisWatchKey)entry.parent();487key.removeChild(entry.name());488}489}490491return false;492}493494/**495* Process directory events. If directory is modified then re-scan496* directory to register any new entries497*/498void processDirectoryEvents(SolarisWatchKey key, int mask) {499if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) {500registerChildren(key.getDirectory(), key,501key.events().contains(StandardWatchEventKinds.ENTRY_CREATE),502key.events().contains(StandardWatchEventKinds.ENTRY_DELETE));503}504}505506/**507* Process events for entries in registered directories. Returns {@code508* true} if events are ignored because the watch key has been cancelled.509*/510boolean processEntryEvents(EntryNode node, int mask) {511SolarisWatchKey key = (SolarisWatchKey)node.parent();512Set<? extends WatchEvent.Kind<?>> events = key.events();513if (events == null) {514// key has been cancelled so ignore event515return true;516}517518// entry modified519if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) &&520events.contains(StandardWatchEventKinds.ENTRY_MODIFY))521{522key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name());523}524525526return false;527}528529/**530* Registers all entries in the given directory531*532* The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters533* indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued534* when new entries are found. When initially registering a directory535* they will always be false. When re-scanning a directory then it536* depends on if the events are enabled or not.537*/538void registerChildren(UnixPath dir,539SolarisWatchKey parent,540boolean sendCreateEvents,541boolean sendDeleteEvents)542{543boolean isModifyEnabled =544parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ;545546// reset visited flag on entries so that we can detect file deletes547for (EntryNode node: parent.children().values()) {548node.setVisited(false);549}550551try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {552for (Path entry: stream) {553Path name = entry.getFileName();554555// skip entry if already registered556EntryNode node = parent.getChild(name);557if (node != null) {558node.setVisited(true);559continue;560}561562// new entry found563564long object = 0L;565int errno = 0;566boolean addNode = false;567568// if ENTRY_MODIFY enabled then we register the entry for events569if (isModifyEnabled) {570try {571UnixPath path = (UnixPath)entry;572int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);573object = registerImpl(path, events);574addNode = true;575} catch (UnixException x) {576errno = x.errno();577}578} else {579addNode = true;580}581582if (addNode) {583// create node584node = new EntryNode(object, (UnixPath)entry.getFileName(), parent);585node.setVisited(true);586// tell the parent about it587parent.addChild(entry.getFileName(), node);588if (object != 0L)589object2Node.put(object, node);590}591592// send ENTRY_CREATE event for the new file593// send ENTRY_DELETE event for files that were deleted immediately594boolean deleted = (errno == ENOENT);595if (sendCreateEvents && (addNode || deleted))596parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);597if (sendDeleteEvents && deleted)598parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);599600}601} catch (DirectoryIteratorException | IOException x) {602// queue OVERFLOW event so that user knows to re-scan directory603parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null);604return;605}606607// clean-up and send ENTRY_DELETE events for any entries that were608// not found609Iterator<Map.Entry<Path,EntryNode>> iterator =610parent.children().entrySet().iterator();611while (iterator.hasNext()) {612Map.Entry<Path,EntryNode> entry = iterator.next();613EntryNode node = entry.getValue();614if (!node.isVisited()) {615long object = node.object();616if (object != 0L) {617object2Node.remove(object);618releaseObject(object, true);619}620if (sendDeleteEvents)621parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name());622iterator.remove();623}624}625}626627/**628* Update watch key's events. If ENTRY_MODIFY changes to be enabled629* then register each file in the direcory; If ENTRY_MODIFY changed to630* be disabled then unregister each file.631*/632void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events)633throws UnixException634{635636// update events, rembering if ENTRY_MODIFY was previously637// enabled or disabled.638boolean oldModifyEnabled = key.events()639.contains(StandardWatchEventKinds.ENTRY_MODIFY);640key.setEvents(events);641642// check if ENTRY_MODIFY has changed643boolean newModifyEnabled = events644.contains(StandardWatchEventKinds.ENTRY_MODIFY);645if (newModifyEnabled != oldModifyEnabled) {646UnixException ex = null;647for (EntryNode node: key.children().values()) {648if (newModifyEnabled) {649// register650UnixPath path = key.getDirectory().resolve(node.name());651int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);652try {653long object = registerImpl(path, ev);654object2Node.put(object, node);655node.setObject(object);656} catch (UnixException x) {657// if file has been deleted then it will be detected658// as a FILE_MODIFIED event on the directory659if (x.errno() != ENOENT) {660ex = x;661break;662}663}664} else {665// unregister666releaseChild(node);667}668}669670// an error occurred671if (ex != null) {672releaseChildren(key);673throw ex;674}675}676}677678/**679* Calls port_associate to register the given path.680* Returns pointer to fileobj structure that is allocated for681* the registration.682*/683long registerImpl(UnixPath dir, int events)684throws UnixException685{686// allocate memory for the path (file_obj->fo_name field)687byte[] path = dir.getByteArrayForSysCalls();688int len = path.length;689long name = unsafe.allocateMemory(len+1);690unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null,691name, (long)len);692unsafe.putByte(name + len, (byte)0);693694// allocate memory for filedatanode structure - this is the object695// to port_associate696long object = unsafe.allocateMemory(SIZEOF_FILEOBJ);697unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0);698unsafe.putAddress(object + OFFSET_FO_NAME, name);699700// associate the object with the port701try {702portAssociate(port,703PORT_SOURCE_FILE,704object,705events);706} catch (UnixException x) {707// debugging708if (x.errno() == EAGAIN) {709System.err.println("The maximum number of objects associated "+710"with the port has been reached");711}712713unsafe.freeMemory(name);714unsafe.freeMemory(object);715throw x;716}717return object;718}719720/**721* Frees all resources for an file_obj object; optionally remove722* association from port723*/724void releaseObject(long object, boolean dissociate) {725// remove association726if (dissociate) {727try {728portDissociate(port, PORT_SOURCE_FILE, object);729} catch (UnixException x) {730// ignore731}732}733734// free native memory735long name = unsafe.getAddress(object + OFFSET_FO_NAME);736unsafe.freeMemory(name);737unsafe.freeMemory(object);738}739}740741/**742* A node with native (file_obj) resources743*/744private static interface Node {745long object();746}747748/**749* A directory node with a map of the entries in the directory750*/751private static interface DirectoryNode extends Node {752void addChild(Path name, EntryNode node);753void removeChild(Path name);754EntryNode getChild(Path name);755}756757/**758* An implementation of a node that is an entry in a directory.759*/760private static class EntryNode implements Node {761private long object;762private final UnixPath name;763private final DirectoryNode parent;764private boolean visited;765766EntryNode(long object, UnixPath name, DirectoryNode parent) {767this.object = object;768this.name = name;769this.parent = parent;770}771772@Override773public long object() {774return object;775}776777void setObject(long ptr) {778this.object = ptr;779}780781UnixPath name() {782return name;783}784785DirectoryNode parent() {786return parent;787}788789boolean isVisited() {790return visited;791}792793void setVisited(boolean v) {794this.visited = v;795}796}797798// -- native methods --799800private static native void init();801802private static native int portCreate() throws UnixException;803804private static native void portAssociate(int port, int source, long object, int events)805throws UnixException;806807private static native void portDissociate(int port, int source, long object)808throws UnixException;809810private static native void portSend(int port, int events)811throws UnixException;812813private static native int portGetn(int port, long address, int max)814throws UnixException;815816static {817AccessController.doPrivileged(new PrivilegedAction<Void>() {818public Void run() {819System.loadLibrary("nio");820return null;821}});822init();823}824}825826827