|
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 configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; |
|
112 enableUploads_ = NO; |
|
113 started_ = NO; |
|
114 NSString* uploadInterval = |
|
115 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; |
|
116 [self setUploadInterval:[uploadInterval intValue]]; |
|
117 } |
|
118 return self; |
|
119 } |
|
120 |
|
121 // Since this class is a singleton, this method is not expected to be called. |
|
122 - (void)dealloc { |
|
123 assert(!breakpadRef_); |
|
124 dispatch_release(queue_); |
|
125 [configuration_ release]; |
|
126 [super dealloc]; |
|
127 } |
|
128 |
|
129 #pragma mark - |
|
130 |
|
131 - (void)start:(BOOL)onCurrentThread { |
|
132 if (started_) |
|
133 return; |
|
134 started_ = YES; |
|
135 void(^startBlock)() = ^{ |
|
136 assert(!breakpadRef_); |
|
137 breakpadRef_ = BreakpadCreate(configuration_); |
|
138 if (breakpadRef_) { |
|
139 BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform()); |
|
140 } |
|
141 }; |
|
142 if (onCurrentThread) |
|
143 startBlock(); |
|
144 else |
|
145 dispatch_async(queue_, startBlock); |
|
146 } |
|
147 |
|
148 - (void)stop { |
|
149 if (!started_) |
|
150 return; |
|
151 started_ = NO; |
|
152 dispatch_sync(queue_, ^{ |
|
153 if (breakpadRef_) { |
|
154 BreakpadRelease(breakpadRef_); |
|
155 breakpadRef_ = NULL; |
|
156 } |
|
157 }); |
|
158 } |
|
159 |
|
160 - (void)setUploadingEnabled:(BOOL)enabled { |
|
161 NSAssert(started_, |
|
162 @"The controller must be started before setUploadingEnabled is called"); |
|
163 dispatch_async(queue_, ^{ |
|
164 if (enabled == enableUploads_) |
|
165 return; |
|
166 if (enabled) { |
|
167 // Set this before calling doSendStoredCrashReport, because that |
|
168 // calls sendDelay, which in turn checks this flag. |
|
169 enableUploads_ = YES; |
|
170 [self sendStoredCrashReports]; |
|
171 } else { |
|
172 enableUploads_ = NO; |
|
173 [NSObject cancelPreviousPerformRequestsWithTarget:self |
|
174 selector:@selector(sendStoredCrashReports) |
|
175 object:nil]; |
|
176 } |
|
177 }); |
|
178 } |
|
179 |
|
180 - (void)updateConfiguration:(NSDictionary*)configuration { |
|
181 NSAssert(!started_, |
|
182 @"The controller must not be started when updateConfiguration is called"); |
|
183 [configuration_ addEntriesFromDictionary:configuration]; |
|
184 NSString* uploadInterval = |
|
185 [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; |
|
186 if (uploadInterval) |
|
187 [self setUploadInterval:[uploadInterval intValue]]; |
|
188 } |
|
189 |
|
190 - (void)setUploadingURL:(NSString*)url { |
|
191 NSAssert(!started_, |
|
192 @"The controller must not be started when setUploadingURL is called"); |
|
193 [configuration_ setValue:url forKey:@BREAKPAD_URL]; |
|
194 } |
|
195 |
|
196 - (void)setUploadInterval:(int)intervalInSeconds { |
|
197 NSAssert(!started_, |
|
198 @"The controller must not be started when setUploadInterval is called"); |
|
199 [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL]; |
|
200 uploadIntervalInSeconds_ = intervalInSeconds; |
|
201 if (uploadIntervalInSeconds_ < 0) |
|
202 uploadIntervalInSeconds_ = 0; |
|
203 } |
|
204 |
|
205 - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key { |
|
206 NSAssert(started_, |
|
207 @"The controller must be started before addUploadParameter is called"); |
|
208 dispatch_async(queue_, ^{ |
|
209 if (breakpadRef_) |
|
210 BreakpadAddUploadParameter(breakpadRef_, key, value); |
|
211 }); |
|
212 } |
|
213 |
|
214 - (void)removeUploadParameterForKey:(NSString*)key { |
|
215 NSAssert(started_, @"The controller must be started before " |
|
216 "removeUploadParameterForKey is called"); |
|
217 dispatch_async(queue_, ^{ |
|
218 if (breakpadRef_) |
|
219 BreakpadRemoveUploadParameter(breakpadRef_, key); |
|
220 }); |
|
221 } |
|
222 |
|
223 - (void)withBreakpadRef:(void(^)(BreakpadRef))callback { |
|
224 NSAssert(started_, |
|
225 @"The controller must be started before withBreakpadRef is called"); |
|
226 dispatch_async(queue_, ^{ |
|
227 callback(breakpadRef_); |
|
228 }); |
|
229 } |
|
230 |
|
231 - (void)hasReportToUpload:(void(^)(BOOL))callback { |
|
232 NSAssert(started_, @"The controller must be started before " |
|
233 "hasReportToUpload is called"); |
|
234 dispatch_async(queue_, ^{ |
|
235 callback(breakpadRef_ && BreakpadHasCrashReportToUpload(breakpadRef_)); |
|
236 }); |
|
237 } |
|
238 |
|
239 #pragma mark - |
|
240 |
|
241 - (int)sendDelay { |
|
242 if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_) |
|
243 return -1; |
|
244 |
|
245 // To prevent overloading the crash server, crashes are not sent than one |
|
246 // report every |uploadIntervalInSeconds_|. A value in the user defaults is |
|
247 // used to keep the time of the last upload. |
|
248 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; |
|
249 NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission]; |
|
250 NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; |
|
251 NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime; |
|
252 |
|
253 if (spanSeconds >= uploadIntervalInSeconds_) |
|
254 return 0; |
|
255 return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds); |
|
256 } |
|
257 |
|
258 - (void)reportWillBeSent { |
|
259 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; |
|
260 [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()] |
|
261 forKey:kLastSubmission]; |
|
262 [userDefaults synchronize]; |
|
263 } |
|
264 |
|
265 - (void)sendStoredCrashReports { |
|
266 dispatch_async(queue_, ^{ |
|
267 if (!BreakpadHasCrashReportToUpload(breakpadRef_)) |
|
268 return; |
|
269 |
|
270 int timeToWait = [self sendDelay]; |
|
271 |
|
272 // Unable to ever send report. |
|
273 if (timeToWait == -1) |
|
274 return; |
|
275 |
|
276 // A report can be sent now. |
|
277 if (timeToWait == 0) { |
|
278 [self reportWillBeSent]; |
|
279 BreakpadUploadNextReport(breakpadRef_); |
|
280 |
|
281 // If more reports must be sent, make sure this method is called again. |
|
282 if (BreakpadHasCrashReportToUpload(breakpadRef_)) |
|
283 timeToWait = uploadIntervalInSeconds_; |
|
284 } |
|
285 |
|
286 // A report must be sent later. |
|
287 if (timeToWait > 0) |
|
288 [self performSelector:@selector(sendStoredCrashReports) |
|
289 withObject:nil |
|
290 afterDelay:timeToWait]; |
|
291 }); |
|
292 } |
|
293 |
|
294 @end |