Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / breakpad / src / client / ios / BreakpadController.mm
1 // Copyright (c) 2012, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #import "BreakpadController.h"
31
32 #import <UIKit/UIKit.h>
33 #include <asl.h>
34 #include <execinfo.h>
35 #include <signal.h>
36 #include <unistd.h>
37 #include <sys/sysctl.h>
38
39 #include <common/scoped_ptr.h>
40
41 #pragma mark -
42 #pragma mark Private Methods
43
44 @interface BreakpadController ()
45
46 // Init the singleton instance.
47 - (id)initSingleton;
48
49 // Load a crash report and send it to the server.
50 - (void)sendStoredCrashReports;
51
52 // Returns when a report can be sent. |-1| means never, |0| means that a report
53 // can be sent immediately, a positive number is the number of seconds to wait
54 // before being allowed to upload a report.
55 - (int)sendDelay;
56
57 // Notifies that a report will be sent, and update the last sending time
58 // accordingly.
59 - (void)reportWillBeSent;
60
61 @end
62
63 #pragma mark -
64 #pragma mark Anonymous namespace
65
66 namespace {
67
68 // The name of the user defaults key for the last submission to the crash
69 // server.
70 NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
71
72 // Returns a NSString describing the current platform.
73 NSString* GetPlatform() {
74   // Name of the system call for getting the platform.
75   static const char kHwMachineSysctlName[] = "hw.machine";
76
77   NSString* result = nil;
78
79   size_t size = 0;
80   if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0)
81     return nil;
82   google_breakpad::scoped_array<char> machine(new char[size]);
83   if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0)
84     result = [NSString stringWithUTF8String:machine.get()];
85   return result;
86 }
87
88 }  // namespace
89
90 #pragma mark -
91 #pragma mark BreakpadController Implementation
92
93 @implementation BreakpadController
94
95 + (BreakpadController*)sharedInstance {
96   @synchronized(self) {
97     static BreakpadController* sharedInstance_ =
98         [[BreakpadController alloc] initSingleton];
99     return sharedInstance_;
100   }
101 }
102
103 - (id)init {
104   return nil;
105 }
106
107 - (id)initSingleton {
108   self = [super init];
109   if (self) {
110     queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
111     enableUploads_ = NO;
112     started_ = NO;
113     [self resetConfiguration];
114   }
115   return self;
116 }
117
118 // Since this class is a singleton, this method is not expected to be called.
119 - (void)dealloc {
120   assert(!breakpadRef_);
121   dispatch_release(queue_);
122   [configuration_ release];
123   [uploadTimeParameters_ release];
124   [super dealloc];
125 }
126
127 #pragma mark -
128
129 - (void)start:(BOOL)onCurrentThread {
130   if (started_)
131     return;
132   started_ = YES;
133   void(^startBlock)() = ^{
134       assert(!breakpadRef_);
135       breakpadRef_ = BreakpadCreate(configuration_);
136       if (breakpadRef_) {
137         BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
138       }
139   };
140   if (onCurrentThread)
141     startBlock();
142   else
143     dispatch_async(queue_, startBlock);
144 }
145
146 - (void)stop {
147   if (!started_)
148     return;
149   started_ = NO;
150   dispatch_sync(queue_, ^{
151       if (breakpadRef_) {
152         BreakpadRelease(breakpadRef_);
153         breakpadRef_ = NULL;
154       }
155   });
156 }
157
158 // This method must be called from the breakpad queue.
159 - (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration
160                                 withBreakpadRef:(BreakpadRef)ref {
161   NSAssert(started_, @"The controller must be started before "
162                      "threadUnsafeSendReportWithConfiguration is called");
163   if (breakpadRef_) {
164     BreakpadUploadReportWithParametersAndConfiguration(breakpadRef_,
165                                                        uploadTimeParameters_,
166                                                        configuration);
167   }
168 }
169
170 - (void)setUploadingEnabled:(BOOL)enabled {
171   NSAssert(started_,
172       @"The controller must be started before setUploadingEnabled is called");
173   dispatch_async(queue_, ^{
174       if (enabled == enableUploads_)
175         return;
176       if (enabled) {
177         // Set this before calling doSendStoredCrashReport, because that
178         // calls sendDelay, which in turn checks this flag.
179         enableUploads_ = YES;
180         [self sendStoredCrashReports];
181       } else {
182         enableUploads_ = NO;
183         [NSObject cancelPreviousPerformRequestsWithTarget:self
184             selector:@selector(sendStoredCrashReports)
185             object:nil];
186       }
187   });
188 }
189
190 - (void)updateConfiguration:(NSDictionary*)configuration {
191   NSAssert(!started_,
192       @"The controller must not be started when updateConfiguration is called");
193   [configuration_ addEntriesFromDictionary:configuration];
194   NSString* uploadInterval =
195       [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
196   if (uploadInterval)
197     [self setUploadInterval:[uploadInterval intValue]];
198 }
199
200 - (void)resetConfiguration {
201   NSAssert(!started_,
202       @"The controller must not be started when resetConfiguration is called");
203   [configuration_ autorelease];
204   configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
205   NSString* uploadInterval =
206       [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
207   [self setUploadInterval:[uploadInterval intValue]];
208   [self setParametersToAddAtUploadTime:nil];
209 }
210
211 - (void)setUploadingURL:(NSString*)url {
212   NSAssert(!started_,
213       @"The controller must not be started when setUploadingURL is called");
214   [configuration_ setValue:url forKey:@BREAKPAD_URL];
215 }
216
217 - (void)setUploadInterval:(int)intervalInSeconds {
218   NSAssert(!started_,
219       @"The controller must not be started when setUploadInterval is called");
220   [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
221   uploadIntervalInSeconds_ = intervalInSeconds;
222   if (uploadIntervalInSeconds_ < 0)
223     uploadIntervalInSeconds_ = 0;
224 }
225
226 - (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters {
227   NSAssert(!started_, @"The controller must not be started when "
228                       "setParametersToAddAtUploadTime is called");
229   [uploadTimeParameters_ autorelease];
230   uploadTimeParameters_ = [uploadTimeParameters copy];
231 }
232
233 - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
234   NSAssert(started_,
235       @"The controller must be started before addUploadParameter is called");
236   dispatch_async(queue_, ^{
237       if (breakpadRef_)
238         BreakpadAddUploadParameter(breakpadRef_, key, value);
239   });
240 }
241
242 - (void)removeUploadParameterForKey:(NSString*)key {
243   NSAssert(started_, @"The controller must be started before "
244                      "removeUploadParameterForKey is called");
245   dispatch_async(queue_, ^{
246       if (breakpadRef_)
247         BreakpadRemoveUploadParameter(breakpadRef_, key);
248   });
249 }
250
251 - (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
252   NSAssert(started_,
253       @"The controller must be started before withBreakpadRef is called");
254   dispatch_async(queue_, ^{
255       callback(breakpadRef_);
256   });
257 }
258
259 - (void)hasReportToUpload:(void(^)(BOOL))callback {
260   NSAssert(started_, @"The controller must be started before "
261                      "hasReportToUpload is called");
262   dispatch_async(queue_, ^{
263       callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0));
264   });
265 }
266
267 - (void)getCrashReportCount:(void(^)(int))callback {
268   NSAssert(started_, @"The controller must be started before "
269                      "getCrashReportCount is called");
270   dispatch_async(queue_, ^{
271       callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0);
272   });
273 }
274
275 - (void)getNextReportConfigurationOrSendDelay:
276     (void(^)(NSDictionary*, int))callback {
277   NSAssert(started_, @"The controller must be started before "
278                      "getNextReportConfigurationOrSendDelay is called");
279   dispatch_async(queue_, ^{
280       if (!breakpadRef_) {
281         callback(nil, -1);
282         return;
283       }
284       int delay = [self sendDelay];
285       if (delay != 0) {
286         callback(nil, delay);
287         return;
288       }
289       [self reportWillBeSent];
290       callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0);
291   });
292 }
293
294 #pragma mark -
295
296 - (int)sendDelay {
297   if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
298     return -1;
299
300   // To prevent overloading the crash server, crashes are not sent than one
301   // report every |uploadIntervalInSeconds_|. A value in the user defaults is
302   // used to keep the time of the last upload.
303   NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
304   NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
305   NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
306   NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
307
308   if (spanSeconds >= uploadIntervalInSeconds_)
309     return 0;
310   return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
311 }
312
313 - (void)reportWillBeSent {
314   NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
315   [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
316                    forKey:kLastSubmission];
317   [userDefaults synchronize];
318 }
319
320 - (void)sendStoredCrashReports {
321   dispatch_async(queue_, ^{
322       if (BreakpadGetCrashReportCount(breakpadRef_) == 0)
323         return;
324
325       int timeToWait = [self sendDelay];
326
327       // Unable to ever send report.
328       if (timeToWait == -1)
329         return;
330
331       // A report can be sent now.
332       if (timeToWait == 0) {
333         [self reportWillBeSent];
334         BreakpadUploadNextReportWithParameters(breakpadRef_,
335                                                uploadTimeParameters_);
336
337         // If more reports must be sent, make sure this method is called again.
338         if (BreakpadGetCrashReportCount(breakpadRef_) > 0)
339           timeToWait = uploadIntervalInSeconds_;
340       }
341
342       // A report must be sent later.
343       if (timeToWait > 0) {
344         // performSelector: doesn't work on queue_
345         dispatch_async(dispatch_get_main_queue(), ^{
346             [self performSelector:@selector(sendStoredCrashReports)
347                        withObject:nil
348                        afterDelay:timeToWait];
349         });
350      }
351   });
352 }
353
354 @end