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