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