| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "rlz/mac/lib/rlz_value_store_mac.h" | |
| 6 | |
| 7 #include "base/mac/foundation_util.h" | |
| 8 #include "base/file_path.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/sys_string_conversions.h" | |
| 11 #include "rlz/lib/assert.h" | |
| 12 #include "rlz/lib/lib_values.h" | |
| 13 #include "rlz/lib/rlz_lib.h" | |
| 14 | |
| 15 #import <Foundation/Foundation.h> | |
| 16 #include <pthread.h> | |
| 17 | |
| 18 using base::mac::ObjCCast; | |
| 19 | |
| 20 namespace rlz_lib { | |
| 21 | |
| 22 // These are written to disk and should not be changed. | |
| 23 NSString* const kPingTimeKey = @"pingTime"; | |
| 24 NSString* const kAccessPointKey = @"accessPoints"; | |
| 25 NSString* const kProductEventKey = @"productEvents"; | |
| 26 NSString* const kStatefulEventKey = @"statefulEvents"; | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 NSString* GetNSProductName(Product product) { | |
| 31 return base::SysUTF8ToNSString(GetProductName(product)); | |
| 32 } | |
| 33 | |
| 34 NSString* GetNSAccessPointName(AccessPoint p) { | |
| 35 return base::SysUTF8ToNSString(GetAccessPointName(p)); | |
| 36 } | |
| 37 | |
| 38 // Retrieves a subdictionary in |p| for key |k|, creating it if necessary. | |
| 39 // If the dictionary contains an object for |k| that is not a mutable | |
| 40 // dictionary, that object is replaced with an empty mutable dictinary. | |
| 41 NSMutableDictionary* GetOrCreateDict( | |
| 42 NSMutableDictionary* p, NSString* k) { | |
| 43 NSMutableDictionary* d = ObjCCast<NSMutableDictionary>([p objectForKey:k]); | |
| 44 if (!d) { | |
| 45 d = [NSMutableDictionary dictionaryWithCapacity:0]; | |
| 46 [p setObject:d forKey:k]; | |
| 47 } | |
| 48 return d; | |
| 49 } | |
| 50 | |
| 51 } // namespace | |
| 52 | |
| 53 RlzValueStoreMac::RlzValueStoreMac(NSMutableDictionary* dict, | |
| 54 NSString* plist_path) | |
| 55 : dict_([dict retain]), plist_path_([plist_path retain]) { | |
| 56 } | |
| 57 | |
| 58 RlzValueStoreMac::~RlzValueStoreMac() { | |
| 59 } | |
| 60 | |
| 61 bool RlzValueStoreMac::HasAccess(AccessType type) { | |
| 62 NSFileManager* manager = [NSFileManager defaultManager]; | |
| 63 switch (type) { | |
| 64 case kReadAccess: return [manager isReadableFileAtPath:plist_path_]; | |
| 65 case kWriteAccess: return [manager isWritableFileAtPath:plist_path_]; | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 bool RlzValueStoreMac::WritePingTime(Product product, int64 time) { | |
| 70 NSNumber* n = [NSNumber numberWithLongLong:time]; | |
| 71 [ProductDict(product) setObject:n forKey:kPingTimeKey]; | |
| 72 return true; | |
| 73 } | |
| 74 | |
| 75 bool RlzValueStoreMac::ReadPingTime(Product product, int64* time) { | |
| 76 if (NSNumber* n = | |
| 77 ObjCCast<NSNumber>([ProductDict(product) objectForKey:kPingTimeKey])) { | |
| 78 *time = [n longLongValue]; | |
| 79 return true; | |
| 80 } | |
| 81 return false; | |
| 82 } | |
| 83 | |
| 84 bool RlzValueStoreMac::ClearPingTime(Product product) { | |
| 85 [ProductDict(product) removeObjectForKey:kPingTimeKey]; | |
| 86 return true; | |
| 87 } | |
| 88 | |
| 89 | |
| 90 bool RlzValueStoreMac::WriteAccessPointRlz(AccessPoint access_point, | |
| 91 const char* new_rlz) { | |
| 92 NSMutableDictionary* d = GetOrCreateDict(WorkingDict(), kAccessPointKey); | |
| 93 [d setObject:base::SysUTF8ToNSString(new_rlz) | |
| 94 forKey:GetNSAccessPointName(access_point)]; | |
| 95 return true; | |
| 96 } | |
| 97 | |
| 98 bool RlzValueStoreMac::ReadAccessPointRlz(AccessPoint access_point, | |
| 99 char* rlz, | |
| 100 size_t rlz_size) { | |
| 101 // Reading a non-existent access point counts as success. | |
| 102 if (NSDictionary* d = ObjCCast<NSDictionary>( | |
| 103 [WorkingDict() objectForKey:kAccessPointKey])) { | |
| 104 NSString* val = ObjCCast<NSString>( | |
| 105 [d objectForKey:GetNSAccessPointName(access_point)]); | |
| 106 if (!val) { | |
| 107 if (rlz_size > 0) | |
| 108 rlz[0] = '\0'; | |
| 109 return true; | |
| 110 } | |
| 111 | |
| 112 std::string s = base::SysNSStringToUTF8(val); | |
| 113 if (s.size() >= rlz_size) { | |
| 114 rlz[0] = 0; | |
| 115 ASSERT_STRING("GetAccessPointRlz: Insufficient buffer size"); | |
| 116 return false; | |
| 117 } | |
| 118 strncpy(rlz, s.c_str(), rlz_size); | |
| 119 return true; | |
| 120 } | |
| 121 if (rlz_size > 0) | |
| 122 rlz[0] = '\0'; | |
| 123 return true; | |
| 124 } | |
| 125 | |
| 126 bool RlzValueStoreMac::ClearAccessPointRlz(AccessPoint access_point) { | |
| 127 if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>( | |
| 128 [WorkingDict() objectForKey:kAccessPointKey])) { | |
| 129 [d removeObjectForKey:GetNSAccessPointName(access_point)]; | |
| 130 } | |
| 131 return true; | |
| 132 } | |
| 133 | |
| 134 | |
| 135 bool RlzValueStoreMac::AddProductEvent(Product product, | |
| 136 const char* event_rlz) { | |
| 137 [GetOrCreateDict(ProductDict(product), kProductEventKey) | |
| 138 setObject:[NSNumber numberWithBool:YES] | |
| 139 forKey:base::SysUTF8ToNSString(event_rlz)]; | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 bool RlzValueStoreMac::ReadProductEvents(Product product, | |
| 144 std::vector<std::string>* events) { | |
| 145 if (NSDictionary* d = ObjCCast<NSDictionary>( | |
| 146 [ProductDict(product) objectForKey:kProductEventKey])) { | |
| 147 for (NSString* s in d) | |
| 148 events->push_back(base::SysNSStringToUTF8(s)); | |
| 149 return true; | |
| 150 } | |
| 151 return true; | |
| 152 } | |
| 153 | |
| 154 bool RlzValueStoreMac::ClearProductEvent(Product product, | |
| 155 const char* event_rlz) { | |
| 156 if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>( | |
| 157 [ProductDict(product) objectForKey:kProductEventKey])) { | |
| 158 [d removeObjectForKey:base::SysUTF8ToNSString(event_rlz)]; | |
| 159 return true; | |
| 160 } | |
| 161 return false; | |
| 162 } | |
| 163 | |
| 164 bool RlzValueStoreMac::ClearAllProductEvents(Product product) { | |
| 165 [ProductDict(product) removeObjectForKey:kProductEventKey]; | |
| 166 return true; | |
| 167 } | |
| 168 | |
| 169 | |
| 170 bool RlzValueStoreMac::AddStatefulEvent(Product product, | |
| 171 const char* event_rlz) { | |
| 172 [GetOrCreateDict(ProductDict(product), kStatefulEventKey) | |
| 173 setObject:[NSNumber numberWithBool:YES] | |
| 174 forKey:base::SysUTF8ToNSString(event_rlz)]; | |
| 175 return true; | |
| 176 } | |
| 177 | |
| 178 bool RlzValueStoreMac::IsStatefulEvent(Product product, | |
| 179 const char* event_rlz) { | |
| 180 if (NSDictionary* d = ObjCCast<NSDictionary>( | |
| 181 [ProductDict(product) objectForKey:kStatefulEventKey])) { | |
| 182 return [d objectForKey:base::SysUTF8ToNSString(event_rlz)] != nil; | |
| 183 } | |
| 184 return false; | |
| 185 } | |
| 186 | |
| 187 bool RlzValueStoreMac::ClearAllStatefulEvents(Product product) { | |
| 188 [ProductDict(product) removeObjectForKey:kStatefulEventKey]; | |
| 189 return true; | |
| 190 } | |
| 191 | |
| 192 | |
| 193 void RlzValueStoreMac::CollectGarbage() { | |
| 194 NOTIMPLEMENTED(); | |
| 195 } | |
| 196 | |
| 197 NSDictionary* RlzValueStoreMac::dictionary() { | |
| 198 return dict_.get(); | |
| 199 } | |
| 200 | |
| 201 NSMutableDictionary* RlzValueStoreMac::WorkingDict() { | |
| 202 std::string brand(SupplementaryBranding::GetBrand()); | |
| 203 if (brand.empty()) | |
| 204 return dict_; | |
| 205 | |
| 206 NSString* brand_ns = | |
| 207 [@"brand_" stringByAppendingString:base::SysUTF8ToNSString(brand)]; | |
| 208 | |
| 209 return GetOrCreateDict(dict_.get(), brand_ns); | |
| 210 } | |
| 211 | |
| 212 NSMutableDictionary* RlzValueStoreMac::ProductDict(Product p) { | |
| 213 return GetOrCreateDict(WorkingDict(), GetNSProductName(p)); | |
| 214 } | |
| 215 | |
| 216 | |
| 217 namespace { | |
| 218 | |
| 219 // Creating a recursive cross-process mutex on windows is one line. On mac, | |
| 220 // there's no primitve for that, so this lock is emulated by an in-process | |
| 221 // mutex to get the recursive part, followed by a cross-process lock for the | |
| 222 // cross-process part. | |
| 223 | |
| 224 // This is a struct so that it doesn't need a static initializer. | |
| 225 struct RecursiveCrossProcessLock { | |
| 226 // Tries to acquire a recursive cross-process lock. Note that this _always_ | |
| 227 // acquires the in-process lock (if it wasn't already acquired). The parent | |
| 228 // directory of |lock_file| must exist. | |
| 229 bool TryGetCrossProcessLock(NSString* lock_filename); | |
| 230 | |
| 231 // Releases the lock. Should always be called, even if | |
| 232 // TryGetCrossProcessLock() returns false. | |
| 233 void ReleaseLock(); | |
| 234 | |
| 235 pthread_mutex_t recursive_lock_; | |
| 236 pthread_t locking_thread_; | |
| 237 | |
| 238 NSDistributedLock* file_lock_; | |
| 239 } g_recursive_lock = { | |
| 240 // PTHREAD_RECURSIVE_MUTEX_INITIALIZER doesn't exist before 10.7 and is buggy | |
| 241 // on 10.7 (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51906#c34), so emulate | |
| 242 // recursive locking with a normal non-recursive mutex. | |
| 243 PTHREAD_MUTEX_INITIALIZER | |
| 244 }; | |
| 245 | |
| 246 bool RecursiveCrossProcessLock::TryGetCrossProcessLock( | |
| 247 NSString* lock_filename) { | |
| 248 bool just_got_lock = false; | |
| 249 | |
| 250 // Emulate a recursive mutex with a non-recursive one. | |
| 251 if (pthread_mutex_trylock(&recursive_lock_) == EBUSY) { | |
| 252 if (pthread_equal(pthread_self(), locking_thread_) == 0) { | |
| 253 // Some other thread has the lock, wait for it. | |
| 254 pthread_mutex_lock(&recursive_lock_); | |
| 255 CHECK(locking_thread_ == 0); | |
| 256 just_got_lock = true; | |
| 257 } | |
| 258 } else { | |
| 259 just_got_lock = true; | |
| 260 } | |
| 261 | |
| 262 locking_thread_ = pthread_self(); | |
| 263 | |
| 264 // Try to acquire file lock. | |
| 265 if (just_got_lock) { | |
| 266 const int kMaxTimeoutMS = 5000; // Matches windows. | |
| 267 const int kSleepPerTryMS = 200; | |
| 268 | |
| 269 CHECK(!file_lock_); | |
| 270 file_lock_ = [[NSDistributedLock alloc] initWithPath:lock_filename]; | |
| 271 | |
| 272 BOOL got_file_lock = NO; | |
| 273 int elapsedMS = 0; | |
| 274 while (!(got_file_lock = [file_lock_ tryLock]) && | |
| 275 elapsedMS < kMaxTimeoutMS) { | |
| 276 usleep(kSleepPerTryMS * 1000); | |
| 277 elapsedMS += kSleepPerTryMS; | |
| 278 } | |
| 279 | |
| 280 if (!got_file_lock) { | |
| 281 [file_lock_ release]; | |
| 282 file_lock_ = nil; | |
| 283 return false; | |
| 284 } | |
| 285 return true; | |
| 286 } else { | |
| 287 return file_lock_ != nil; | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 void RecursiveCrossProcessLock::ReleaseLock() { | |
| 292 if (file_lock_) { | |
| 293 [file_lock_ unlock]; | |
| 294 [file_lock_ release]; | |
| 295 file_lock_ = nil; | |
| 296 } | |
| 297 | |
| 298 locking_thread_ = 0; | |
| 299 pthread_mutex_unlock(&recursive_lock_); | |
| 300 } | |
| 301 | |
| 302 | |
| 303 // This is set during test execution, to write RLZ files into a temporary | |
| 304 // directory instead of the user's Application Support folder. | |
| 305 NSString* g_test_folder; | |
| 306 | |
| 307 // RlzValueStoreMac keeps its data in memory and only writes it to disk when | |
| 308 // ScopedRlzValueStoreLock goes out of scope. Hence, if several | |
| 309 // ScopedRlzValueStoreLocks are nested, they all need to use the same store | |
| 310 // object. | |
| 311 | |
| 312 // This counts the nesting depth. | |
| 313 int g_lock_depth = 0; | |
| 314 | |
| 315 // This is the store object that might be shared. Only set if g_lock_depth > 0. | |
| 316 RlzValueStoreMac* g_store_object = NULL; | |
| 317 | |
| 318 | |
| 319 NSString* CreateRlzDirectory() { | |
| 320 NSFileManager* manager = [NSFileManager defaultManager]; | |
| 321 NSArray* paths = NSSearchPathForDirectoriesInDomains( | |
| 322 NSApplicationSupportDirectory, NSUserDomainMask, /*expandTilde=*/YES); | |
| 323 NSString* folder = nil; | |
| 324 if ([paths count] > 0) | |
| 325 folder = ObjCCast<NSString>([paths objectAtIndex:0]); | |
| 326 if (!folder) | |
| 327 folder = [@"~/Library/Application Support" stringByStandardizingPath]; | |
| 328 folder = [folder stringByAppendingPathComponent:@"Google/RLZ"]; | |
| 329 | |
| 330 if (g_test_folder) | |
| 331 folder = [g_test_folder stringByAppendingPathComponent:folder]; | |
| 332 | |
| 333 [manager createDirectoryAtPath:folder | |
| 334 withIntermediateDirectories:YES | |
| 335 attributes:nil | |
| 336 error:nil]; | |
| 337 return folder; | |
| 338 } | |
| 339 | |
| 340 // Returns the path of the rlz plist store, also creates the parent directory | |
| 341 // path if it doesn't exist. | |
| 342 NSString* RlzPlistFilename() { | |
| 343 NSString* const kRlzFile = @"RlzStore.plist"; | |
| 344 return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; | |
| 345 } | |
| 346 | |
| 347 // Returns the path of the rlz lock file, also creates the parent directory | |
| 348 // path if it doesn't exist. | |
| 349 NSString* RlzLockFilename() { | |
| 350 NSString* const kRlzFile = @"lockfile"; | |
| 351 return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; | |
| 352 } | |
| 353 | |
| 354 } // namespace | |
| 355 | |
| 356 ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() { | |
| 357 bool got_distributed_lock = | |
| 358 g_recursive_lock.TryGetCrossProcessLock(RlzLockFilename()); | |
| 359 // At this point, we hold the in-process lock, no matter the value of | |
| 360 // |got_distributed_lock|. | |
| 361 | |
| 362 ++g_lock_depth; | |
| 363 | |
| 364 if (!got_distributed_lock) { | |
| 365 // Give up. |store_| isn't set, which signals to callers that acquiring | |
| 366 // the lock failed. |g_recursive_lock| will be released by the | |
| 367 // destructor. | |
| 368 CHECK(!g_store_object); | |
| 369 return; | |
| 370 } | |
| 371 | |
| 372 if (g_lock_depth > 1) { | |
| 373 // Reuse the already existing store object. | |
| 374 CHECK(g_store_object); | |
| 375 store_.reset(g_store_object); | |
| 376 return; | |
| 377 } | |
| 378 | |
| 379 CHECK(!g_store_object); | |
| 380 | |
| 381 NSString* plist = RlzPlistFilename(); | |
| 382 | |
| 383 // Create an empty file if none exists yet. | |
| 384 NSFileManager* manager = [NSFileManager defaultManager]; | |
| 385 if (![manager fileExistsAtPath:plist isDirectory:NULL]) | |
| 386 [[NSDictionary dictionary] writeToFile:plist atomically:YES]; | |
| 387 | |
| 388 NSMutableDictionary* dict = | |
| 389 [NSMutableDictionary dictionaryWithContentsOfFile:plist]; | |
| 390 VERIFY(dict); | |
| 391 | |
| 392 if (dict) { | |
| 393 store_.reset(new RlzValueStoreMac(dict, plist)); | |
| 394 g_store_object = (RlzValueStoreMac*)store_.get(); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() { | |
| 399 --g_lock_depth; | |
| 400 CHECK(g_lock_depth >= 0); | |
| 401 | |
| 402 if (g_lock_depth > 0) { | |
| 403 // Other locks are still using store_, don't free it yet. | |
| 404 ignore_result(store_.release()); | |
| 405 return; | |
| 406 } | |
| 407 | |
| 408 if (store_.get()) { | |
| 409 g_store_object = NULL; | |
| 410 | |
| 411 NSDictionary* dict = | |
| 412 static_cast<RlzValueStoreMac*>(store_.get())->dictionary(); | |
| 413 VERIFY([dict writeToFile:RlzPlistFilename() atomically:YES]); | |
| 414 } | |
| 415 | |
| 416 // Check that "store_ set" => "file_lock acquired". The converse isn't true, | |
| 417 // for example if the rlz data file can't be read. | |
| 418 if (store_.get()) | |
| 419 CHECK(g_recursive_lock.file_lock_); | |
| 420 if (!g_recursive_lock.file_lock_) | |
| 421 CHECK(!store_.get()); | |
| 422 | |
| 423 g_recursive_lock.ReleaseLock(); | |
| 424 } | |
| 425 | |
| 426 RlzValueStore* ScopedRlzValueStoreLock::GetStore() { | |
| 427 return store_.get(); | |
| 428 } | |
| 429 | |
| 430 namespace testing { | |
| 431 | |
| 432 void SetRlzStoreDirectory(const FilePath& directory) { | |
| 433 base::mac::ScopedNSAutoreleasePool pool; | |
| 434 | |
| 435 [g_test_folder release]; | |
| 436 if (directory.empty()) { | |
| 437 g_test_folder = nil; | |
| 438 } else { | |
| 439 // Not Unsafe on OS X. | |
| 440 g_test_folder = | |
| 441 [[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()]; | |
| 442 } | |
| 443 } | |
| 444 | |
| 445 } // namespace testing | |
| 446 | |
| 447 } // namespace rlz_lib | |
| OLD | NEW |