PageRenderTime 64ms CodeModel.GetById 17ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  1//
  2//  GTMFileSystemKQueue.m
  3//
  4//  Copyright 2008 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9//
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11//
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19#import "GTMFileSystemKQueue.h"
 20#import <unistd.h>
 21#import "GTMDefines.h"
 22#import "GTMDebugSelectorValidation.h"
 23#import "GTMTypeCasting.h"
 24
 25// File descriptor for the kqueue that will hold all of our file system events.
 26static int gFileSystemKQueueFileDescriptor = 0;
 27
 28// A wrapper around the kqueue file descriptor so we can put it into a
 29// runloop.
 30static CFSocketRef gRunLoopSocket = NULL;
 31
 32
 33@interface GTMFileSystemKQueue (PrivateMethods)
 34- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags;
 35- (void)addFileDescriptorMonitor:(int)fd;
 36- (int)registerWithKQueue;
 37- (void)unregisterWithKQueue;
 38@end
 39
 40
 41@implementation GTMFileSystemKQueue
 42
 43-(id)init {
 44  // Folks shouldn't call init directly, so they get what they deserve.
 45  _GTMDevLog(@"Don't call init, use "
 46              @"initWithPath:forEvents:acrossReplace:target:action:");
 47  return [self initWithPath:nil forEvents:0 acrossReplace:NO
 48                     target:nil action:nil];
 49}
 50
 51
 52- (id)initWithPath:(NSString *)path
 53         forEvents:(GTMFileSystemKQueueEvents)events
 54     acrossReplace:(BOOL)acrossReplace
 55            target:(id)target
 56            action:(SEL)action {
 57
 58  if ((self = [super init])) {
 59
 60    fd_ = -1;
 61    path_ = [path copy];
 62    events_ = events;
 63    acrossReplace_ = acrossReplace;
 64
 65    target_ = target;  // Don't retain since target will most likely retain us.
 66    action_ = action;
 67    if ([path_ length] == 0 || !events_ || !target_ || !action_) {
 68      [self release];
 69      return nil;
 70    }
 71
 72    // Make sure it imples what we expect
 73    GTMAssertSelectorNilOrImplementedWithArguments(target_,
 74                                                   action_,
 75                                                   @encode(GTMFileSystemKQueue*),
 76                                                   @encode(GTMFileSystemKQueueEvents),
 77                                                   NULL);
 78
 79    fd_ = [self registerWithKQueue];
 80    if (fd_ < 0) {
 81      [self release];
 82      return nil;
 83    }
 84  }
 85  return self;
 86}
 87
 88- (void)dealloc {
 89  [self unregisterWithKQueue];
 90  [path_ release];
 91
 92  [super dealloc];
 93}
 94
 95- (NSString *)path {
 96  return path_;
 97}
 98
 99// Cribbed from Advanced Mac OS X Programming.
100static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type,
101                           CFDataRef address, const void *data, void *info) {
102  // We're using CFRunLoop calls here. Even when used on the main thread, they
103  // don't trigger the draining of the main application's autorelease pool that
104  // NSRunLoop provides. If we're used in a UI-less app, this means that
105  // autoreleased objects would never go away, so we provide our own pool here.
106  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
107
108  // We want to read as many events as possible so loop on the kevent call
109  // till the kqueue is empty.
110  int events = -1;
111  do {
112    // We wouldn't be here if CFSocket didn't think there was data on
113    // |gFileSystemKQueueFileDescriptor|. However, since this callback is tied
114    // to the runloop, if [... unregisterWithKQueue] was called before a runloop
115    // spin we may now be looking at an empty queue (remember,
116    // |gFileSystemKQueueFileDescriptor| isn't a normal descriptor).
117
118    // Try to consume one event with an immediate timeout.
119    struct kevent event;
120    const struct timespec noWait = { 0, 0 };
121    events = kevent(gFileSystemKQueueFileDescriptor, NULL, 0, &event, 1, &noWait);
122
123    if (events == 1) {
124      GTMFileSystemKQueue *fskq = GTM_STATIC_CAST(GTMFileSystemKQueue,
125                                                  event.udata);
126      [fskq notify:event.fflags];
127    } else if (events == -1) {
128      _GTMDevLog(@"could not pick up kqueue event.  Errno %d", errno);  // COV_NF_LINE
129    } else {
130      // |events| is zero, either we've drained the kqueue or CFSocket was
131      // notified and then the events went away before we had a chance to see
132      // them.
133    }
134  } while (events > 0);
135
136  [pool drain];
137}
138
139// Cribbed from Advanced Mac OS X Programming
140- (void)addFileDescriptorMonitor:(int)fd {
141  _GTMDevAssert(gRunLoopSocket == NULL, @"socket should be NULL at this point");
142
143  CFSocketContext context = { 0, NULL, NULL, NULL, NULL };
144  gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
145                                            fd,
146                                            kCFSocketReadCallBack,
147                                            SocketCallBack,
148                                            &context);
149  if (gRunLoopSocket == NULL) {
150    _GTMDevLog(@"could not CFSocketCreateWithNative");  // COV_NF_LINE
151    goto bailout;   // COV_NF_LINE
152  }
153
154  CFRunLoopSourceRef rls;
155  rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0);
156  if (rls == NULL) {
157    _GTMDevLog(@"could not create a run loop source");  // COV_NF_LINE
158    goto bailout;  // COV_NF_LINE
159  }
160
161  CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
162                     kCFRunLoopDefaultMode);
163  CFRelease(rls);
164
165 bailout:
166  return;
167
168}
169
170// Returns the FD we got in registering
171- (int)registerWithKQueue {
172
173  // Make sure we have our kqueue.
174  if (gFileSystemKQueueFileDescriptor == 0) {
175    gFileSystemKQueueFileDescriptor = kqueue();
176
177    if (gFileSystemKQueueFileDescriptor == -1) {
178      // COV_NF_START
179      _GTMDevLog(@"could not make filesystem kqueue.  Errno %d", errno);
180      return -1;
181      // COV_NF_END
182    }
183
184    // Add the kqueue file descriptor to the runloop.
185    [self addFileDescriptorMonitor:gFileSystemKQueueFileDescriptor];
186  }
187
188  int newFD = open([path_ fileSystemRepresentation], O_EVTONLY, 0);
189  if (newFD >= 0) {
190
191    // Add a new event for the file.
192    struct kevent filter;
193    EV_SET(&filter, newFD, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
194           events_, 0, self);
195
196    const struct timespec noWait = { 0, 0 };
197    if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) == -1) {
198      // COV_NF_START
199      _GTMDevLog(@"could not add event for path %@.  Errno %d", path_, errno);
200      close(newFD);
201      newFD = -1;
202      // COV_NF_END
203    }
204  }
205
206  return newFD;
207}
208
209- (void)unregisterWithKQueue {
210  // Short-circuit cases where we didn't actually register a kqueue event.
211  if (fd_ < 0) return;
212
213  struct kevent filter;
214  EV_SET(&filter, fd_, EVFILT_VNODE, EV_DELETE, 0, 0, self);
215
216  const struct timespec noWait = { 0, 0 };
217  if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
218    _GTMDevLog(@"could not remove event for path %@.  Errno %d", path_, errno);  // COV_NF_LINE
219  }
220
221  // Now close the file down
222  close(fd_);
223  fd_ = -1;
224}
225
226
227- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags {
228
229  // Some notifications get a little bit of overhead first
230
231  if (eventFFlags & NOTE_REVOKE) {
232    // COV_NF_START - no good way to do this in a unittest
233    // Assume revoke means unmount and give up
234    [self unregisterWithKQueue];
235    // COV_NF_END
236  }
237
238  if (eventFFlags & NOTE_DELETE) {
239    [self unregisterWithKQueue];
240    if (acrossReplace_) {
241      fd_ = [self registerWithKQueue];
242    }
243  }
244
245  if (eventFFlags & NOTE_RENAME) {
246    // If we're doing it across replace, we move to the new fd for the new file
247    // that might have come onto the path.  if we aren't doing accross replace,
248    // nothing to do, just stay on the file.
249    if (acrossReplace_) {
250      int newFD = [self registerWithKQueue];
251      if (newFD >= 0) {
252        [self unregisterWithKQueue];
253        fd_ = newFD;
254      }
255    }
256  }
257
258  // Now, fire the selector
259  NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_];
260  _GTMDevAssert(methodSig != nil, @"failed to get the signature?");
261  NSInvocation *invocation
262    = [NSInvocation invocationWithMethodSignature:methodSig];
263  [invocation setTarget:target_];
264  [invocation setSelector:action_];
265  [invocation setArgument:&self atIndex:2];
266  [invocation setArgument:&eventFFlags atIndex:3];
267  [invocation invoke];
268}
269
270@end