/core/externals/update-engine/externals/google-toolbox-for-mac/Foundation/GTMFileSystemKQueue.m
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