/core/externals/update-engine/externals/google-toolbox-for-mac/Foundation/GTMFileSystemKQueue.m
http://macfuse.googlecode.com/ · Objective C · 270 lines · 164 code · 49 blank · 57 comment · 36 complexity · 63afe2fd66a71b1d0073e706b7470ab3 MD5 · raw file
- //
- // 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 <unistd.h>
- #import "GTMDefines.h"
- #import "GTMDebugSelectorValidation.h"
- #import "GTMTypeCasting.h"
- // 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_;
- }
- // 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];
- }
- // 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