// // GTMFileSystemKQueue.m // // Copyright 2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // #import "GTMFileSystemKQueue.h" #import #import "GTMDefines.h" #import "GTMDebugSelectorValidation.h" #import "GTMTypeCasting.h" #pragma clang diagnostic push // Ignore all of the deprecation warnings for GTMFileSystemKQueue #pragma clang diagnostic ignored "-Wdeprecated-implementations" // File descriptor for the kqueue that will hold all of our file system events. static int gFileSystemKQueueFileDescriptor = 0; // A wrapper around the kqueue file descriptor so we can put it into a // runloop. static CFSocketRef gRunLoopSocket = NULL; @interface GTMFileSystemKQueue (PrivateMethods) - (void)notify:(GTMFileSystemKQueueEvents)eventFFlags; - (void)addFileDescriptorMonitor:(int)fd; - (int)registerWithKQueue; - (void)unregisterWithKQueue; @end @implementation GTMFileSystemKQueue -(id)init { // Folks shouldn't call init directly, so they get what they deserve. _GTMDevLog(@"Don't call init, use " @"initWithPath:forEvents:acrossReplace:target:action:"); return [self initWithPath:nil forEvents:0 acrossReplace:NO target:nil action:nil]; } - (id)initWithPath:(NSString *)path forEvents:(GTMFileSystemKQueueEvents)events acrossReplace:(BOOL)acrossReplace target:(id)target action:(SEL)action { if ((self = [super init])) { fd_ = -1; path_ = [path copy]; events_ = events; acrossReplace_ = acrossReplace; target_ = target; // Don't retain since target will most likely retain us. action_ = action; if ([path_ length] == 0 || !events_ || !target_ || !action_) { [self release]; return nil; } // Make sure it imples what we expect GTMAssertSelectorNilOrImplementedWithArguments(target_, action_, @encode(GTMFileSystemKQueue*), @encode(GTMFileSystemKQueueEvents), NULL); fd_ = [self registerWithKQueue]; if (fd_ < 0) { [self release]; return nil; } } return self; } - (void)dealloc { [self unregisterWithKQueue]; [path_ release]; [super dealloc]; } - (NSString *)path { return path_; } #pragma clang diagnostic push // Ignore all of the deprecation warnings for GTMFileSystemKQueue #pragma clang diagnostic ignored "-Wdeprecated-declarations" // Cribbed from Advanced Mac OS X Programming. static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { // We're using CFRunLoop calls here. Even when used on the main thread, they // don't trigger the draining of the main application's autorelease pool that // NSRunLoop provides. If we're used in a UI-less app, this means that // autoreleased objects would never go away, so we provide our own pool here. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // We want to read as many events as possible so loop on the kevent call // till the kqueue is empty. int events = -1; do { // We wouldn't be here if CFSocket didn't think there was data on // |gFileSystemKQueueFileDescriptor|. However, since this callback is tied // to the runloop, if [... unregisterWithKQueue] was called before a runloop // spin we may now be looking at an empty queue (remember, // |gFileSystemKQueueFileDescriptor| isn't a normal descriptor). // Try to consume one event with an immediate timeout. struct kevent event; const struct timespec noWait = { 0, 0 }; events = kevent(gFileSystemKQueueFileDescriptor, NULL, 0, &event, 1, &noWait); if (events == 1) { GTMFileSystemKQueue *fskq = GTM_STATIC_CAST(GTMFileSystemKQueue, event.udata); [fskq notify:event.fflags]; } else if (events == -1) { _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE } else { // |events| is zero, either we've drained the kqueue or CFSocket was // notified and then the events went away before we had a chance to see // them. } } while (events > 0); [pool drain]; } #pragma clang diagnostic pop // Cribbed from Advanced Mac OS X Programming - (void)addFileDescriptorMonitor:(int)fd { _GTMDevAssert(gRunLoopSocket == NULL, @"socket should be NULL at this point"); CFSocketContext context = { 0, NULL, NULL, NULL, NULL }; gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault, fd, kCFSocketReadCallBack, SocketCallBack, &context); if (gRunLoopSocket == NULL) { _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE goto bailout; // COV_NF_LINE } CFRunLoopSourceRef rls; rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0); if (rls == NULL) { _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE goto bailout; // COV_NF_LINE } CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); bailout: return; } // Returns the FD we got in registering - (int)registerWithKQueue { // Make sure we have our kqueue. if (gFileSystemKQueueFileDescriptor == 0) { gFileSystemKQueueFileDescriptor = kqueue(); if (gFileSystemKQueueFileDescriptor == -1) { // COV_NF_START _GTMDevLog(@"could not make filesystem kqueue. Errno %d", errno); return -1; // COV_NF_END } // Add the kqueue file descriptor to the runloop. [self addFileDescriptorMonitor:gFileSystemKQueueFileDescriptor]; } int newFD = open([path_ fileSystemRepresentation], O_EVTONLY, 0); if (newFD >= 0) { // Add a new event for the file. struct kevent filter; EV_SET(&filter, newFD, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, events_, 0, self); const struct timespec noWait = { 0, 0 }; if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) == -1) { // COV_NF_START _GTMDevLog(@"could not add event for path %@. Errno %d", path_, errno); close(newFD); newFD = -1; // COV_NF_END } } return newFD; } - (void)unregisterWithKQueue { // Short-circuit cases where we didn't actually register a kqueue event. if (fd_ < 0) return; struct kevent filter; EV_SET(&filter, fd_, EVFILT_VNODE, EV_DELETE, 0, 0, self); const struct timespec noWait = { 0, 0 }; if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) { _GTMDevLog(@"could not remove event for path %@. Errno %d", path_, errno); // COV_NF_LINE } // Now close the file down close(fd_); fd_ = -1; } - (void)notify:(GTMFileSystemKQueueEvents)eventFFlags { // Some notifications get a little bit of overhead first if (eventFFlags & NOTE_REVOKE) { // COV_NF_START - no good way to do this in a unittest // Assume revoke means unmount and give up [self unregisterWithKQueue]; // COV_NF_END } if (eventFFlags & NOTE_DELETE) { [self unregisterWithKQueue]; if (acrossReplace_) { fd_ = [self registerWithKQueue]; } } if (eventFFlags & NOTE_RENAME) { // If we're doing it across replace, we move to the new fd for the new file // that might have come onto the path. if we aren't doing accross replace, // nothing to do, just stay on the file. if (acrossReplace_) { int newFD = [self registerWithKQueue]; if (newFD >= 0) { [self unregisterWithKQueue]; fd_ = newFD; } } } // Now, fire the selector NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_]; _GTMDevAssert(methodSig != nil, @"failed to get the signature?"); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; [invocation setTarget:target_]; [invocation setSelector:action_]; [invocation setArgument:&self atIndex:2]; [invocation setArgument:&eventFFlags atIndex:3]; [invocation invoke]; } @end #pragma clang diagnostic pop