• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
66namespace {
67
68// The name of the user defaults key for the last submission to the crash
69// server.
70NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission";
71
72// Returns a NSString describing the current platform.
73NSString* 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  static dispatch_once_t onceToken;
97  static BreakpadController* sharedInstance ;
98  dispatch_once(&onceToken, ^{
99      sharedInstance = [[BreakpadController alloc] initSingleton];
100  });
101  return sharedInstance;
102}
103
104- (id)init {
105  return nil;
106}
107
108- (id)initSingleton {
109  self = [super init];
110  if (self) {
111    queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL);
112    enableUploads_ = NO;
113    started_ = NO;
114    [self resetConfiguration];
115  }
116  return self;
117}
118
119// Since this class is a singleton, this method is not expected to be called.
120- (void)dealloc {
121  assert(!breakpadRef_);
122  dispatch_release(queue_);
123  [configuration_ release];
124  [uploadTimeParameters_ release];
125  [super dealloc];
126}
127
128#pragma mark -
129
130- (void)start:(BOOL)onCurrentThread {
131  if (started_)
132    return;
133  started_ = YES;
134  void(^startBlock)() = ^{
135      assert(!breakpadRef_);
136      breakpadRef_ = BreakpadCreate(configuration_);
137      if (breakpadRef_) {
138        BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform());
139      }
140  };
141  if (onCurrentThread)
142    startBlock();
143  else
144    dispatch_async(queue_, startBlock);
145}
146
147- (void)stop {
148  if (!started_)
149    return;
150  started_ = NO;
151  dispatch_sync(queue_, ^{
152      if (breakpadRef_) {
153        BreakpadRelease(breakpadRef_);
154        breakpadRef_ = NULL;
155      }
156  });
157}
158
159- (BOOL)isStarted {
160  return started_;
161}
162
163// This method must be called from the breakpad queue.
164- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration
165                                withBreakpadRef:(BreakpadRef)ref {
166  NSAssert(started_, @"The controller must be started before "
167                     "threadUnsafeSendReportWithConfiguration is called");
168  if (breakpadRef_) {
169    BreakpadUploadReportWithParametersAndConfiguration(
170        breakpadRef_, uploadTimeParameters_, configuration,
171        uploadCompleteCallback_);
172  }
173}
174
175- (void)setUploadingEnabled:(BOOL)enabled {
176  NSAssert(started_,
177      @"The controller must be started before setUploadingEnabled is called");
178  dispatch_async(queue_, ^{
179      if (enabled == enableUploads_)
180        return;
181      if (enabled) {
182        // Set this before calling doSendStoredCrashReport, because that
183        // calls sendDelay, which in turn checks this flag.
184        enableUploads_ = YES;
185        [self sendStoredCrashReports];
186      } else {
187        // disable the enableUpload_ flag.
188        // sendDelay checks this flag and disables the upload of logs by sendStoredCrashReports
189        enableUploads_ = NO;
190      }
191  });
192}
193
194- (void)updateConfiguration:(NSDictionary*)configuration {
195  NSAssert(!started_,
196      @"The controller must not be started when updateConfiguration is called");
197  [configuration_ addEntriesFromDictionary:configuration];
198  NSString *uploadInterval =
199      [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
200  if (uploadInterval)
201    [self setUploadInterval:[uploadInterval intValue]];
202}
203
204- (void)resetConfiguration {
205  NSAssert(!started_,
206      @"The controller must not be started when resetConfiguration is called");
207  [configuration_ autorelease];
208  configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy];
209  NSString *uploadInterval =
210      [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL];
211  [self setUploadInterval:[uploadInterval intValue]];
212  [self setParametersToAddAtUploadTime:nil];
213}
214
215- (void)setUploadingURL:(NSString*)url {
216  NSAssert(!started_,
217      @"The controller must not be started when setUploadingURL is called");
218  [configuration_ setValue:url forKey:@BREAKPAD_URL];
219}
220
221- (void)setUploadInterval:(int)intervalInSeconds {
222  NSAssert(!started_,
223      @"The controller must not be started when setUploadInterval is called");
224  [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL];
225  uploadIntervalInSeconds_ = intervalInSeconds;
226  if (uploadIntervalInSeconds_ < 0)
227    uploadIntervalInSeconds_ = 0;
228}
229
230- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters {
231  NSAssert(!started_, @"The controller must not be started when "
232                      "setParametersToAddAtUploadTime is called");
233  [uploadTimeParameters_ autorelease];
234  uploadTimeParameters_ = [uploadTimeParameters copy];
235}
236
237- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key {
238  NSAssert(started_,
239      @"The controller must be started before addUploadParameter is called");
240  dispatch_async(queue_, ^{
241      if (breakpadRef_)
242        BreakpadAddUploadParameter(breakpadRef_, key, value);
243  });
244}
245
246- (void)setUploadCallback:(BreakpadUploadCompletionCallback)callback {
247  NSAssert(started_,
248           @"The controller must not be started before setUploadCallback is "
249            "called");
250  dispatch_async(queue_, ^{
251    uploadCompleteCallback_ = callback;
252  });
253}
254
255- (void)removeUploadParameterForKey:(NSString*)key {
256  NSAssert(started_, @"The controller must be started before "
257                     "removeUploadParameterForKey is called");
258  dispatch_async(queue_, ^{
259      if (breakpadRef_)
260        BreakpadRemoveUploadParameter(breakpadRef_, key);
261  });
262}
263
264- (void)withBreakpadRef:(void(^)(BreakpadRef))callback {
265  dispatch_async(queue_, ^{
266      callback(started_ ? breakpadRef_ : NULL);
267  });
268}
269
270- (void)hasReportToUpload:(void(^)(BOOL))callback {
271  NSAssert(started_, @"The controller must be started before "
272                     "hasReportToUpload is called");
273  dispatch_async(queue_, ^{
274      callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0));
275  });
276}
277
278- (void)getCrashReportCount:(void(^)(int))callback {
279  NSAssert(started_, @"The controller must be started before "
280                     "getCrashReportCount is called");
281  dispatch_async(queue_, ^{
282      callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0);
283  });
284}
285
286- (void)getNextReportConfigurationOrSendDelay:
287    (void(^)(NSDictionary*, int))callback {
288  NSAssert(started_, @"The controller must be started before "
289                     "getNextReportConfigurationOrSendDelay is called");
290  dispatch_async(queue_, ^{
291      if (!breakpadRef_) {
292        callback(nil, -1);
293        return;
294      }
295      int delay = [self sendDelay];
296      if (delay != 0) {
297        callback(nil, delay);
298        return;
299      }
300      [self reportWillBeSent];
301      callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0);
302  });
303}
304
305- (void)getDateOfMostRecentCrashReport:(void(^)(NSDate *))callback {
306  NSAssert(started_, @"The controller must be started before "
307           "getDateOfMostRecentCrashReport is called");
308  dispatch_async(queue_, ^{
309    if (!breakpadRef_) {
310      callback(nil);
311      return;
312    }
313    callback(BreakpadGetDateOfMostRecentCrashReport(breakpadRef_));
314  });
315}
316
317#pragma mark -
318
319- (int)sendDelay {
320  if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_)
321    return -1;
322
323  // To prevent overloading the crash server, crashes are not sent than one
324  // report every |uploadIntervalInSeconds_|. A value in the user defaults is
325  // used to keep the time of the last upload.
326  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
327  NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission];
328  NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
329  NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime;
330
331  if (spanSeconds >= uploadIntervalInSeconds_)
332    return 0;
333  return uploadIntervalInSeconds_ - static_cast<int>(spanSeconds);
334}
335
336- (void)reportWillBeSent {
337  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
338  [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()]
339                   forKey:kLastSubmission];
340  [userDefaults synchronize];
341}
342
343// This method must be called from the breakpad queue.
344- (void)sendStoredCrashReports {
345  if (BreakpadGetCrashReportCount(breakpadRef_) == 0)
346    return;
347
348  int timeToWait = [self sendDelay];
349
350  // Unable to ever send report.
351  if (timeToWait == -1)
352    return;
353
354  // A report can be sent now.
355  if (timeToWait == 0) {
356    [self reportWillBeSent];
357    BreakpadUploadNextReportWithParameters(breakpadRef_, uploadTimeParameters_,
358                                           uploadCompleteCallback_);
359
360    // If more reports must be sent, make sure this method is called again.
361    if (BreakpadGetCrashReportCount(breakpadRef_) > 0)
362      timeToWait = uploadIntervalInSeconds_;
363  }
364
365  // A report must be sent later.
366  if (timeToWait > 0) {
367    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeToWait * NSEC_PER_SEC));
368    dispatch_after(delay, queue_, ^{
369        [self sendStoredCrashReports];
370    });
371  }
372}
373
374@end
375