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 |