/* * Copyright 2008-2011 Uwe Pachler * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. This particular file is * subject to the "Classpath" exception as provided in the LICENSE file * that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * */ package name.pachler.nio.file.impl; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; import name.pachler.nio.file.ClosedWatchServiceException; import name.pachler.nio.file.Path; import name.pachler.nio.file.WatchEvent.Kind; import name.pachler.nio.file.WatchEvent.Modifier; import name.pachler.nio.file.WatchKey; import name.pachler.nio.file.ext.ExtendedWatchEventKind; import static name.pachler.nio.file.impl.BSD.*; /** * * @author count */ public class BSDPathWatchService extends PathWatchService{ /* private static class KeventID{ int ident; // kevent's ident member int filter; // kevent's filter member } */ // the kqueue() file descriptor private int kqueuefd = -1; // mapping between a path string and KeventID - used to check if // the path is already being watched by this watch service private Map<String, Integer> dirs = new HashMap<String, Integer>(); // maps a KevetnID to a watch key. Used to find the watch key for an event // that reports activity on the watched directory private Map<Integer, PollingPathWatchKey> keys = new HashMap<Integer, PollingPathWatchKey>(); private int closePipeReadFd; private int closePipeWriteFd; final private Object changeLock = new Object(); private Set<PathWatchKey> signalledWatchKeys = new HashSet<PathWatchKey>(); private Queue<PathWatchKey> pendingWatchKeys = new LinkedList<PathWatchKey>(); private static final long DEFAULT_POLLING_INTERVAL_MILLIS = 2000; // 2 seconds private long pollingIntervalMillis = DEFAULT_POLLING_INTERVAL_MILLIS; private int numKeysRequiringPolling; public BSDPathWatchService(){ try { String propertyValue = System.getProperty("name.pachler.io.file.BSDPathWatchService.pollingIntervalMillis", Long.toString(DEFAULT_POLLING_INTERVAL_MILLIS)); pollingIntervalMillis = Long.parseLong(propertyValue); }catch(Throwable t){ // ignore, pllingIntervalMillis will still have its default value. } open(); } @Override public synchronized PathWatchKey register(Path path, Kind<?>[] kinds, Modifier[] modifiers) throws IOException { PathImpl pathImpl = checkAndCastToPathImpl(path); int flags = makeFlagMask(kinds, modifiers); // check that user only provided supported flags and modifiers int supportedFlags = (FLAG_FILTER_ENTRY_CREATE | FLAG_FILTER_ENTRY_DELETE | FLAG_FILTER_ENTRY_MODIFY | FLAG_FILTER_KEY_INVALID); if((flags & ~supportedFlags) != 0) throw new UnsupportedOperationException("The given watch event kind or modifier is not supported by this WatchService"); String pathname = pathImpl.getFile().getAbsolutePath(); PollingPathWatchKey key = null; // request changeLock BSD.write(closePipeWriteFd, new byte[1], 1); synchronized(changeLock){ if(kqueuefd == -1) throw new ClosedWatchServiceException(); { Integer dirfdInteger = dirs.get(pathname); if(dirfdInteger != null) key = keys.get(dirfdInteger); } if(key == null){ // no directory file fd registered - we'll need to open // one now // create file descriptor and watch event first boolean success = false; int dirfd = -1; try { dirfd = BSD.open(pathname, BSD.O_RDONLY, 0); if(dirfd == -1) throw new IOException("error registering the path with the native OS: " + strerror(errno())); kevent e = new kevent(); e.set_ident(dirfd); e.set_filter(EVFILT_VNODE); e.set_flags((short)(EV_ADD | EV_CLEAR)); e.set_fflags(NOTE_WRITE | NOTE_DELETE | NOTE_REVOKE); int result = kevent(kqueuefd, new kevent[]{e}, null, null); // do we need more specific error handling here? if(result != 0) throw new IOException("error registering the path with the native OS: " + strerror(errno())); // create watch key and add it to the key and dirs maps key = new PollingPathWatchKey(this, path, 0); keys.put(dirfd, key); dirs.put(pathname, dirfd); } finally { // if something went wrong, close descriptors if(key == null){ if(dirfd != -1){ // if the descriptor has been added to the kqueue, // it will be removed automatically when the descriptor // closes. BSD.close(dirfd); } } } } if(key != null && key.getFlags() != flags){ // check if modification flag has changed; moddiff will // be +1 if the ENTRY_MODIFIED flag was added, -1 if // it was removed (and 0 if it didn't change) int moddiff = 0; moddiff += (flags & FLAG_FILTER_ENTRY_MODIFY)!=0 ? +1 : 0; moddiff += (key.getFlags() & FLAG_FILTER_ENTRY_MODIFY)!=0 ? -1 : 0; numKeysRequiringPolling += moddiff; key.setFlags(flags); } // retract request for change lock BSD.read(closePipeReadFd, new byte[1], 1); } // first poll to capture initial state key.poll(); return key; } @Override synchronized void cancel(PathWatchKey pathWatchKey) { // request change lock byte[] b = new byte[1]; write(closePipeWriteFd, b, 1); synchronized(changeLock){ boolean eventsAdded = cancelImpl(pathWatchKey, true); if(eventsAdded) queueKey(pathWatchKey); // clear request int nread = read(closePipeReadFd, b, 1); assert(nread == 1); } } private boolean cancelImpl(PathWatchKey pathWatchKey, boolean removeKey){ PathImpl pathImpl = (PathImpl)pathWatchKey.getPath(); String pathString = pathImpl.getFile().getPath(); Integer dirfdInteger = dirs.get(pathString); // check if the key that was passed in is ours if(dirfdInteger == null) return false; // FIXME: should throw an exception in this case PathWatchKey key = keys.get(dirfdInteger); if(key != pathWatchKey) return false; // FIXME: should throw an exception in this case boolean eventAdded = false; if((key.getFlags() & FLAG_FILTER_KEY_INVALID) != 0) { key.addWatchEvent(new VoidWatchEvent(ExtendedWatchEventKind.KEY_INVALID)); eventAdded = true; } // if we get here, the key is ours, so we'll invalidate it and // remove it now. int dirfd = dirfdInteger.intValue(); kevent[] changelist = new kevent[]{ new kevent() }; changelist[0].set_ident(dirfd); changelist[0].set_filter(EVFILT_VNODE); changelist[0].set_flags(EV_DELETE); int result = kevent(kqueuefd, changelist, null, null); assert(result == 0); key.invalidate(); if((key.getFlags() & FLAG_FILTER_ENTRY_MODIFY) != 0) --numKeysRequiringPolling; if (removeKey) { keys.remove(dirfdInteger); } dirs.remove(pathString); return eventAdded; } @Override public synchronized boolean reset(PathWatchKey pathWatchKey) { if(!pathWatchKey.isValid()) return false; if(pathWatchKey.hasPendingWatchEvents()) pendingWatchKeys.add(pathWatchKey); else signalledWatchKeys.remove(pathWatchKey); return true; } private void open(){ kqueuefd = kqueue(); int[] pipefd = new int[2]; int pipeResult = pipe(pipefd); closePipeReadFd = pipefd[0]; closePipeWriteFd = pipefd[1]; } @Override public synchronized void close() throws IOException { // request change lock byte[] b = new byte[1]; write(closePipeWriteFd, new byte[]{0}, 1); synchronized(changeLock){ // close all file descriptors BSD.close(kqueuefd); kqueuefd = -1; int nread = read(closePipeReadFd, b, 1); assert(nread == 1); BSD.close(closePipeReadFd); BSD.close(closePipeWriteFd); } } @Override public WatchKey poll() throws InterruptedException, ClosedWatchServiceException { return pollImpl(0); } @Override public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException, ClosedWatchServiceException { long millis = TimeUnit.MILLISECONDS.convert(timeout, unit); return pollImpl(millis); } @Override public WatchKey take() throws InterruptedException, ClosedWatchServiceException { return pollImpl(-1); } private WatchKey pollImpl(long timeout) throws InterruptedException, ClosedWatchServiceException { long lastStart = System.currentTimeMillis(); do{ // if there is a timeout specified, count down timeout if(timeout != -1){ long currentTime = System.currentTimeMillis(); long lastDuration = currentTime - lastStart; timeout -= lastDuration; if(timeout < 0) timeout = 0; lastStart = currentTime; } kevent[] eventlist = new kevent[32]; // FIXME: this should be chosen depending on whether we need to // poll or not. long selectTimeout = timeout; if((timeout == -1 || timeout > pollingIntervalMillis) && numKeysRequiringPolling > 0) selectTimeout = pollingIntervalMillis; int nread = 0; synchronized(changeLock){ // if we have pending watches, we're done and return the first one. if(pendingWatchKeys.size() > 0) return pendingWatchKeys.remove(); // check if watch key has been closed if(kqueuefd == -1) throw new ClosedWatchServiceException(); int[] readfds = {closePipeReadFd, kqueuefd }; int selectResult = select(readfds, null, null, selectTimeout); if(selectResult == -1){ // check for interruption if(BSD.errno() == BSD.EINTR) throw new InterruptedException(); // otherwise, this is another error that shouldn't occur // here. String message = BSD.strerror(BSD.errno()); try { close(); } finally { // the message string here is just for debugging throw new ClosedWatchServiceException(); } } if(readfds[0] == closePipeReadFd){ // we have been requested to release the changeLock continue; } // we know now that kevent() will not block, because select() told us so... if(readfds[1] == kqueuefd) nread = kevent(kqueuefd, null, eventlist, null); if(nread == -1){ if(nread == EINTR) throw new InterruptedException(); try { close(); } finally { // catch exception and throw ClosedWatchServiceException instead throw new ClosedWatchServiceException(); } } if(nread > 0) { // go through all kevent structures and update keys for(int i=0; i<nread; ++i){ kevent e = eventlist[i]; int dirfd = (int)e.get_ident(); int fflags = e.get_fflags(); PollingPathWatchKey key = keys.get(dirfd); // in some cases, the key might not be there any more because // it was invalidated (and therefore cancelled) in response to // a previous kevent if(key == null) continue; boolean eventsAdded; // check if watch key has become invalid if((fflags & NOTE_REVOKE)!=0) eventsAdded = cancelImpl(key, true); else{ try { // poll key's directory eventsAdded = key.poll(); } catch (FileNotFoundException ex) { eventsAdded = cancelImpl(key, true); } } if(eventsAdded) queueKey(key); } } else if(numKeysRequiringPolling > 0){ // if we timed out and we have keys that need to be polled Set<Entry<Integer, PollingPathWatchKey>> entrySet = keys.entrySet(); Iterator<Entry<Integer, PollingPathWatchKey>> iterator = entrySet.iterator(); while(iterator.hasNext()) { Entry<Integer, PollingPathWatchKey> entry = iterator.next(); PollingPathWatchKey key = entry.getValue(); // only poll keys that have the modification flag set - // CREATE/DELETE are flagged in kevent if((key.getFlags() & FLAG_FILTER_ENTRY_MODIFY) == 0) continue; boolean eventsAdded; try { eventsAdded = key.poll(); } catch (FileNotFoundException ex) { eventsAdded = cancelImpl(key, false); iterator.remove(); } if(eventsAdded) queueKey(key); } } // now check for pending watch keys again if(pendingWatchKeys.size() > 0) return pendingWatchKeys.remove(); } // synchronized(changeLock) } while(timeout > 0 || timeout == -1); return null; } private void queueKey(PathWatchKey key) { // if this key has pending events and is not signalled // yet, set it to signalled. if(!signalledWatchKeys.contains(key)) { signalledWatchKeys.add(key); pendingWatchKeys.add(key); } } } /* @generated */