/core/externals/update-engine/Core/KSTicketStore.m

http://macfuse.googlecode.com/ · Objective C · 248 lines · 150 code · 49 blank · 49 comment · 27 complexity · 4ec1881d3c8c5b93e128797988e8d43b MD5 · raw file

  1. // Copyright 2008 Google Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "KSTicketStore.h"
  15. #import "KSTicket.h"
  16. #import <fcntl.h>
  17. #import <unistd.h>
  18. // Implementation note: Tickets are stored internally in a dictionary
  19. // keyed by each ticket's ProductID. This means that no two tickets
  20. // can have the same ProductID, which is exactly the behavior that we
  21. // want.
  22. @interface KSTicketStore (PrivateMethods)
  23. - (NSMutableDictionary *)readTicketMap;
  24. - (BOOL)writeTicketMap:(NSMutableDictionary *)ticketMap;
  25. - (NSMutableDictionary *)atomicReadTicketMap;
  26. - (BOOL)atomicStoreTicket:(KSTicket *)ticket;
  27. - (BOOL)atomicDeleteTicket:(KSTicket *)ticket;
  28. @end
  29. @implementation KSTicketStore
  30. + (id)ticketStoreWithPath:(NSString *)path {
  31. return [[[self alloc] initWithPath:path] autorelease];
  32. }
  33. - (id)initWithPath:(NSString *)path {
  34. if ((self = [super init])) {
  35. path_ = [path copy];
  36. if (path_ == nil) {
  37. [self release];
  38. return nil;
  39. }
  40. }
  41. return self;
  42. }
  43. - (id)init {
  44. return [self initWithPath:nil];
  45. }
  46. - (void)dealloc {
  47. [path_ release];
  48. [super dealloc];
  49. }
  50. - (NSString *)description {
  51. return [NSString stringWithFormat:@"<%@:%p path=%@>",
  52. [self class], self, path_];
  53. }
  54. - (NSString *)path {
  55. return path_;
  56. }
  57. - (int)ticketCount {
  58. return [[self tickets] count];
  59. }
  60. // We never want to return nil here. If we don't have any tickets, then we'll
  61. // set an empty array of them, and return that empty array.
  62. - (NSArray *)tickets {
  63. NSDictionary *map = [self atomicReadTicketMap];
  64. return map ? [map allValues] : [NSArray array];
  65. }
  66. - (KSTicket *)ticketForProductID:(NSString *)productid {
  67. if (productid == nil) return nil;
  68. productid = [productid lowercaseString];
  69. KSTicket *ticket = nil;
  70. NSEnumerator *ticketEnumerator = [[self tickets] objectEnumerator];
  71. while ((ticket = [ticketEnumerator nextObject])) {
  72. // Do case insensitive but case preserving comparison by lowercasing
  73. if ([productid isEqualToString:[[ticket productID] lowercaseString]])
  74. break;
  75. }
  76. return ticket;
  77. }
  78. - (BOOL)storeTicket:(KSTicket *)ticket {
  79. return [self atomicStoreTicket:ticket];
  80. }
  81. - (BOOL)deleteTicket:(KSTicket *)ticket {
  82. return [self atomicDeleteTicket:ticket];
  83. }
  84. - (BOOL)deleteTicketForProductID:(NSString *)productid {
  85. KSTicket *ticket = [self ticketForProductID:productid];
  86. return [self deleteTicket:ticket];
  87. }
  88. @end // KSTicketStore
  89. @implementation KSTicketStore (PrivateMethods)
  90. - (NSMutableDictionary *)readTicketMap {
  91. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  92. NSData *data = [NSData dataWithContentsOfFile:path_];
  93. if ([data length] == 0) return nil;
  94. NSDictionary *map = nil;
  95. @try {
  96. map = [NSKeyedUnarchiver unarchiveObjectWithData:data];
  97. // COV_NF_START
  98. }
  99. @catch (id ex) {
  100. GTMLoggerError(@"Caught exception unarchiving ticket store at %@: %@",
  101. path_, ex);
  102. }
  103. // COV_NF_END
  104. return [[map mutableCopy] autorelease];
  105. }
  106. - (BOOL)writeTicketMap:(NSMutableDictionary *)ticketMap {
  107. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  108. if (ticketMap == nil) return NO;
  109. BOOL ok = NO;
  110. @try {
  111. ok = [NSKeyedArchiver archiveRootObject:ticketMap toFile:path_];
  112. // COV_NF_START
  113. }
  114. @catch (id ex) {
  115. GTMLoggerError(@"Caught exception archiving ticket map to %@: %@",
  116. path_, ex);
  117. }
  118. // COV_NF_END
  119. return ok;
  120. }
  121. // Locking
  122. //
  123. // The next few "atomic" methods use these macros to lock the ticket store file
  124. // at path_ before accessing it. We do this with a simple advisory lock. The
  125. // lock file itself will be |path_| with a file extension of ".lock".
  126. //
  127. // Also, we do this using macros to make clear the very simple nature of this
  128. // locking mechanism: these locks are not recursive, and aren't smart enough to
  129. // "NOP" if the process already has the lock. If we wrapped this logic up in a
  130. // full-blown object, one may be led to think that the locking mechanism is much
  131. // more sophisticated than it really is.
  132. //
  133. // The LOCK_STORE macro simple opens the ticket store path_ with the O_EXLOCK
  134. // flag set. If this fails, it returns NO. The UNLOCK_STORE() macro simply
  135. // closes the fd. These two methods must be balanced, and there must be no code
  136. // paths that could lead to a LOCK_STORE() being called w/o a corresponding
  137. // UNLOCK_STORE(). Note that since these locks are based on FDs, when the
  138. // process exists (even if it crashes), the advisory locks will be released.
  139. #define LOCK_STORE() \
  140. NSString *_lockPath_ = [path_ stringByAppendingPathExtension:@"lock"]; \
  141. int _oflags_ = O_CREAT | O_RDONLY | O_EXLOCK; \
  142. int _lockFD_ = open([_lockPath_ fileSystemRepresentation], _oflags_, 0444); \
  143. if (_lockFD_ < 0) return NO // COV_NF_LINE
  144. #define UNLOCK_STORE() \
  145. if (_lockFD_ >= 0) close(_lockFD_)
  146. - (NSMutableDictionary *)atomicReadTicketMap {
  147. LOCK_STORE();
  148. NSMutableDictionary *map = [self readTicketMap];
  149. UNLOCK_STORE();
  150. return map;
  151. }
  152. // Takes the lock and adds |ticket| to the on-disk ticket store.
  153. - (BOOL)atomicStoreTicket:(KSTicket *)ticket {
  154. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  155. if (ticket == nil) return NO;
  156. LOCK_STORE();
  157. NSMutableDictionary *map = [self readTicketMap];
  158. if (map == nil) map = [NSMutableDictionary dictionary];
  159. // Store in a case insensitive but case preserving manner
  160. [map setObject:ticket forKey:[[ticket productID] lowercaseString]];
  161. BOOL ok = [self writeTicketMap:map];
  162. UNLOCK_STORE();
  163. return ok;
  164. }
  165. // Takes the lock and deletes |ticket| from the on-disk ticket store.
  166. - (BOOL)atomicDeleteTicket:(KSTicket *)ticket {
  167. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  168. if (ticket == nil) return NO;
  169. LOCK_STORE();
  170. NSMutableDictionary *map = [self readTicketMap];
  171. // Delete in a case insensitive manner
  172. [map removeObjectForKey:[[ticket productID] lowercaseString]];
  173. BOOL ok = [self writeTicketMap:map];
  174. UNLOCK_STORE();
  175. return ok;
  176. }
  177. @end // PrivateMethods
  178. @implementation NSArray (TicketRetrieving)
  179. - (NSDictionary *)ticketsByURL {
  180. NSMutableDictionary *urlToTickets = [NSMutableDictionary dictionary];
  181. KSTicket *ticket = nil;
  182. NSEnumerator *ticketEnumerator = [self objectEnumerator];
  183. while ((ticket = [ticketEnumerator nextObject])) {
  184. // Return nil if we find an object in the array that's not a KSTicket
  185. if (![ticket isKindOfClass:[KSTicket class]]) {
  186. // COV_NF_START
  187. GTMLoggerError(@"%@ is not a KSTicket, returning nil", ticket);
  188. return nil;
  189. // COV_NF_END
  190. }
  191. NSURL *key = [ticket serverURL];
  192. NSMutableArray *tickets = [urlToTickets objectForKey:key];
  193. if (tickets == nil) tickets = [NSMutableArray array];
  194. [tickets addObject:ticket];
  195. [urlToTickets setObject:tickets forKey:key];
  196. }
  197. return [urlToTickets count] > 0 ? urlToTickets : nil;
  198. }
  199. @end // TicketStoreQuerying