~ubuntu-branches/ubuntu/karmic/gears/karmic

« back to all changes in this revision

Viewing changes to gears/crash_sender/crash_sender_osx.m

  • Committer: Bazaar Package Importer
  • Author(s): Stefan Lesicnik
  • Date: 2009-04-30 19:15:25 UTC
  • Revision ID: james.westby@ubuntu.com-20090430191525-0790sb5wzg8ou0xb
Tags: upstream-0.5.21.0~svn3334+dfsg
ImportĀ upstreamĀ versionĀ 0.5.21.0~svn3334+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2008, Google Inc.
 
2
//
 
3
// Redistribution and use in source and binary forms, with or without
 
4
// modification, are permitted provided that the following conditions are met:
 
5
//
 
6
//  1. Redistributions of source code must retain the above copyright notice,
 
7
//     this list of conditions and the following disclaimer.
 
8
//  2. Redistributions in binary form must reproduce the above copyright notice,
 
9
//     this list of conditions and the following disclaimer in the documentation
 
10
//     and/or other materials provided with the distribution.
 
11
//  3. Neither the name of Google Inc. nor the names of its contributors may be
 
12
//     used to endorse or promote products derived from this software without
 
13
//     specific prior written permission.
 
14
//
 
15
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 
16
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 
17
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 
18
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
19
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 
20
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 
21
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 
22
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 
23
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 
24
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
25
 
 
26
#import <pwd.h>
 
27
#import <sys/stat.h>
 
28
#import <unistd.h>
 
29
 
 
30
#import <SystemConfiguration/SystemConfiguration.h>
 
31
 
 
32
#import "third_party/breakpad_osx/src/common/mac/HTTPMultipartUpload.h"
 
33
#import "gears/base/common/exception_handler_osx/nshost_macaddress.h"
 
34
 
 
35
#import "gears/crash_sender/crash_sender_osx.h"
 
36
 
 
37
#define kLastSubmission @"LastSubmission"
 
38
 
 
39
// Gears: Increase default crash limit of 200K -> 800K.
 
40
const int kMinidumpFileLengthLimit = 800000;
 
41
 
 
42
#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
 
43
 
 
44
@interface Reporter(PrivateMethods)
 
45
+ (uid_t)consoleUID;
 
46
 
 
47
- (id)initWithConfigurationFD:(int)fd;
 
48
 
 
49
- (NSString *)readString;
 
50
- (NSData *)readData:(size_t)length;
 
51
 
 
52
- (BOOL)readConfigurationData;
 
53
- (BOOL)readMinidumpData;
 
54
 
 
55
- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport;
 
56
- (BOOL)shouldSubmitReport;
 
57
@end
 
58
 
 
59
@implementation Reporter
 
60
//=============================================================================
 
61
+ (uid_t)consoleUID {
 
62
  SCDynamicStoreRef store =
 
63
    SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Reporter"), NULL, NULL);
 
64
  uid_t uid = -2;  // Default to "nobody"
 
65
  if (store) {
 
66
    CFStringRef user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL);
 
67
 
 
68
    if (user)
 
69
      CFRelease(user);
 
70
    else
 
71
      uid = -2;
 
72
 
 
73
    CFRelease(store);
 
74
  }
 
75
 
 
76
  return uid;
 
77
}
 
78
 
 
79
//=============================================================================
 
80
- (id)initWithConfigurationFD:(int)fd {
 
81
  if ((self = [super init])) {
 
82
    configFile_ = fd;
 
83
  }
 
84
  
 
85
  // Because the reporter is embedded in the framework (and many copies
 
86
  // of the framework may exist) its not completely certain that the OS
 
87
  // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
 
88
  // Info.plist. To make sure, also set the key directly if needed.
 
89
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
 
90
  if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
 
91
    [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
 
92
  }
 
93
 
 
94
  return self;
 
95
}
 
96
 
 
97
//=============================================================================
 
98
- (NSString *)readString {
 
99
  NSMutableString *str = [NSMutableString stringWithCapacity:32];
 
100
  char ch[2] = { 0 };
 
101
 
 
102
  while (read(configFile_, &ch[0], 1) == 1) {
 
103
    if (ch[0] == '\n') {
 
104
      // Break if this is the first newline after reading some other string
 
105
      // data.
 
106
      if ([str length])
 
107
        break;
 
108
    } else {
 
109
      [str appendString:[NSString stringWithUTF8String:ch]];
 
110
    }
 
111
  }
 
112
 
 
113
  return str;
 
114
}
 
115
 
 
116
//=============================================================================
 
117
- (NSData *)readData:(size_t)length {
 
118
  NSMutableData *data = [NSMutableData dataWithLength:length];
 
119
  char *bytes = (char *)[data bytes];
 
120
 
 
121
  if (read(configFile_, bytes, length) != length)
 
122
    return nil;
 
123
 
 
124
  return data;
 
125
}
 
126
 
 
127
//=============================================================================
 
128
- (BOOL)readConfigurationData {
 
129
  parameters_ = [[NSMutableDictionary alloc] init];
 
130
 
 
131
  while (1) {
 
132
    NSString *key = [self readString];
 
133
 
 
134
    if (![key length])
 
135
      break;
 
136
 
 
137
    // Read the data.  Try to convert to a UTF-8 string, or just save
 
138
    // the data
 
139
    NSString *lenStr = [self readString];
 
140
    size_t len = [lenStr intValue];
 
141
    NSData *data = [self readData:len];
 
142
    id value = [[NSString alloc] initWithData:data
 
143
                                     encoding:NSUTF8StringEncoding];
 
144
 
 
145
    [parameters_ setObject:value ? value : data forKey:key];
 
146
    [value release];
 
147
  }
 
148
 
 
149
#if 0
 
150
  // !!@ JUST FOR TESTING
 
151
  [parameters_ setObject:@"******** THIS IS NOT A REAL CRASH - JUST TESTING!! *******************"
 
152
    forKey:@"TestingMessage1"];
 
153
  [parameters_ setObject:@"******** THIS IS NOT A REAL CRASH - IGNORE!! *******************"
 
154
    forKey:@"TestingMessage2"];
 
155
  [parameters_ setObject:@"******** THIS IS NOT A REAL CRASH - IGNORE!! *******************"
 
156
    forKey:@"TestingMessage3"];
 
157
  [parameters_ setObject:@"******** THIS IS NOT A REAL CRASH - IGNORE!! *******************"
 
158
    forKey:@"TestingMessage4"];
 
159
#endif
 
160
 
 
161
  // generate a unique client ID based on this host's MAC address
 
162
  // then add a key/value pair for it
 
163
  NSString *clientID = [[NSHost currentHost] obfuscatedMACAddress];
 
164
  [parameters_ setObject:clientID forKey:@"guid"];
 
165
 
 
166
  close(configFile_);
 
167
  configFile_ = -1;
 
168
 
 
169
  return YES;
 
170
}
 
171
 
 
172
//=============================================================================
 
173
- (BOOL)readMinidumpData {
 
174
  NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
 
175
  NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
 
176
 
 
177
  if (![minidumpID length])
 
178
    return NO;
 
179
 
 
180
  NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
 
181
  path = [path stringByAppendingPathExtension:@"dmp"];
 
182
 
 
183
  // check the size of the minidump and limit it to a reasonable size
 
184
  // before attempting to load into memory and upload
 
185
  const char *fileName = [path fileSystemRepresentation];
 
186
  struct stat fileStatus;
 
187
  
 
188
  BOOL success = YES;
 
189
 
 
190
  if (!stat(fileName, &fileStatus)) {
 
191
    if (fileStatus.st_size > kMinidumpFileLengthLimit) {
 
192
      fprintf(stderr, "GoogleBreakpad Reporter: minidump file too large to upload : %d\n",
 
193
        (int)fileStatus.st_size);
 
194
      success = NO;
 
195
    }
 
196
  } else {
 
197
      fprintf(stderr, "GoogleBreakpad Reporter: unable to determine minidump file length\n");
 
198
      success = NO;
 
199
  }
 
200
 
 
201
  if (success) {
 
202
    minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
 
203
    success = ([minidumpContents_ length] ? YES : NO);
 
204
  }
 
205
  
 
206
  if (!success) {
 
207
    // something wrong with the minidump file -- delete it
 
208
    unlink(fileName);
 
209
  }
 
210
  
 
211
  return success;
 
212
}
 
213
 
 
214
//=============================================================================
 
215
- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport {
 
216
  // Send without confirmation
 
217
  if ([[parameters_ objectForKey:@GOOGLE_BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"])
 
218
    return YES;
 
219
 
 
220
  NSString *display = [parameters_ objectForKey:@GOOGLE_BREAKPAD_PRODUCT_DISPLAY];
 
221
 
 
222
  if (![display length])
 
223
    display = [parameters_ objectForKey:@GOOGLE_BREAKPAD_PRODUCT];
 
224
 
 
225
  NSBundle *bundle = [NSBundle mainBundle];
 
226
  NSString *header = [NSString stringWithFormat:
 
227
    NSLocalizedStringFromTableInBundle(@"headerFmt", nil, bundle, @""), display];
 
228
 
 
229
  // Setup the localized dialog
 
230
  NSMutableDictionary *noteDict = [NSMutableDictionary dictionary];
 
231
  [noteDict setObject:header
 
232
               forKey:(NSString *)kCFUserNotificationAlertHeaderKey];
 
233
 
 
234
  // If we're going to submit a report, prompt the user if this is okay.
 
235
  // Otherwise, just let them know that the app crashed.
 
236
  float timeout = 60.0;  // timeout value for the user notification
 
237
  
 
238
  if (shouldSubmitReport) {
 
239
    [noteDict setObject:NSLocalizedStringFromTableInBundle(@"msg", nil, bundle, @"")
 
240
                 forKey:(NSString *)kCFUserNotificationAlertMessageKey];
 
241
    [noteDict setObject:NSLocalizedStringFromTableInBundle(@"sendReportButton", nil, bundle, @"")
 
242
                 forKey:(NSString *)kCFUserNotificationDefaultButtonTitleKey];
 
243
    [noteDict setObject:NSLocalizedStringFromTableInBundle(@"cancelButton", nil, bundle, @"")
 
244
                 forKey:(NSString *)kCFUserNotificationAlternateButtonTitleKey];
 
245
 
 
246
    // Nominally use the report interval
 
247
    timeout = [[parameters_ objectForKey:@GOOGLE_BREAKPAD_REPORT_INTERVAL]
 
248
    floatValue];
 
249
  } else {
 
250
    [noteDict setObject:NSLocalizedStringFromTableInBundle(@"msgNoSend", nil, bundle, @"")
 
251
                 forKey:(NSString *)kCFUserNotificationAlertMessageKey];
 
252
    [noteDict setObject:NSLocalizedStringFromTableInBundle(@"noSendButton", nil, bundle, @"")
 
253
                 forKey:(NSString *)kCFUserNotificationDefaultButtonTitleKey];
 
254
    timeout = 60.0;
 
255
  }
 
256
 
 
257
  // show the notification for at least one minute
 
258
  if (timeout < 60.0) {
 
259
    timeout = 60.0;
 
260
  }
 
261
  
 
262
  // Show the notification
 
263
  CFOptionFlags flags = kCFUserNotificationCautionAlertLevel;
 
264
  SInt32 error;
 
265
  CFUserNotificationRef note =
 
266
    CFUserNotificationCreate(NULL, timeout, flags, &error, (CFDictionaryRef)noteDict);
 
267
  CFUserNotificationReceiveResponse(note, timeout, &flags);
 
268
 
 
269
  return flags == 0;
 
270
}
 
271
 
 
272
//=============================================================================
 
273
- (BOOL)shouldSubmitReport {
 
274
  float interval = [[parameters_ objectForKey:@GOOGLE_BREAKPAD_REPORT_INTERVAL]
 
275
    floatValue];
 
276
  NSString *program = [parameters_ objectForKey:@GOOGLE_BREAKPAD_PRODUCT];
 
277
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
 
278
  NSMutableDictionary *programDict =
 
279
    [NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
 
280
  NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission];
 
281
  NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
 
282
  NSTimeInterval now = CFAbsoluteTimeGetCurrent();
 
283
  NSTimeInterval spanSeconds = (now - lastTime);
 
284
 
 
285
  [programDict setObject:[NSNumber numberWithFloat:now] forKey:kLastSubmission];
 
286
  [ud setObject:programDict forKey:program];
 
287
  [ud synchronize];
 
288
 
 
289
  // If we've specified an interval and we're within that time, don't ask the
 
290
  // user if we should report
 
291
  if (interval > spanSeconds)
 
292
    return NO;
 
293
 
 
294
  return YES;
 
295
}
 
296
 
 
297
//=============================================================================
 
298
- (void)report {
 
299
  NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@GOOGLE_BREAKPAD_URL]];
 
300
  HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
 
301
  NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
 
302
 
 
303
  // Set the known parameters.  This should be kept up to date with the
 
304
  // parameters defined in the GoogleBreakpad.h list of parameters.  The intent
 
305
  // is so that if there's a parameter that's not in this list, we consider
 
306
  // it to be a "user-defined" parameter and we'll upload it to the server.
 
307
  NSSet *knownParameters =
 
308
    [NSSet setWithObjects:@kReporterMinidumpDirectoryKey,
 
309
      @kReporterMinidumpIDKey, @GOOGLE_BREAKPAD_PRODUCT_DISPLAY,
 
310
      @GOOGLE_BREAKPAD_PRODUCT, @GOOGLE_BREAKPAD_VERSION, @GOOGLE_BREAKPAD_URL,
 
311
      @GOOGLE_BREAKPAD_REPORT_INTERVAL, @GOOGLE_BREAKPAD_SKIP_CONFIRM,
 
312
      @GOOGLE_BREAKPAD_SEND_AND_EXIT, nil];
 
313
 
 
314
  // Add parameters
 
315
  [uploadParameters setObject:[parameters_ objectForKey:@GOOGLE_BREAKPAD_PRODUCT]
 
316
                 forKey:@"prod"];
 
317
  [uploadParameters setObject:[parameters_ objectForKey:@GOOGLE_BREAKPAD_VERSION]
 
318
                 forKey:@"ver"];
 
319
 
 
320
  // Add any user parameters
 
321
  NSArray *parameterKeys = [parameters_ allKeys];
 
322
  int keyCount = [parameterKeys count];
 
323
  int i;
 
324
  for (i = 0; i < keyCount; ++i) {
 
325
    NSString *key = [parameterKeys objectAtIndex:i];
 
326
    if (![knownParameters containsObject:key])
 
327
      [uploadParameters setObject:[parameters_ objectForKey:key] forKey:key];
 
328
  }
 
329
 
 
330
  [upload setParameters:uploadParameters];
 
331
 
 
332
  // Add minidump file
 
333
  [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
 
334
 
 
335
  // Send it
 
336
  NSError *error = nil;
 
337
  NSData *data = [upload send:&error];
 
338
  NSString *result = [[NSString alloc] initWithData:data
 
339
                                           encoding:NSUTF8StringEncoding];
 
340
  const char *reportID = "ERR";
 
341
 
 
342
  if (error)
 
343
    fprintf(stderr, "GoogleBreakpad Reporter: Send Error: %s\n",
 
344
            [[error description] UTF8String]);
 
345
  else
 
346
    reportID = [result UTF8String];
 
347
 
 
348
  // rename the minidump file according to the id returned from the server
 
349
  NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
 
350
  NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
 
351
 
 
352
  NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
 
353
    minidumpDir, minidumpID];
 
354
  NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
 
355
    minidumpDir, reportID];
 
356
 
 
357
  const char *src = [srcString fileSystemRepresentation];
 
358
  const char *dest = [destString fileSystemRepresentation];
 
359
  
 
360
  if (rename(src, dest) == 0) {
 
361
    fprintf(stderr, "GoogleBreakpad Reporter: Renamed %s to %s after successful upload\n",
 
362
            src, dest);
 
363
  }
 
364
  else {
 
365
    // can't rename - don't worry - it's not important for users
 
366
    fprintf(stderr, "GoogleBreakpad Reporter: successful upload report ID = %s\n",
 
367
            reportID );
 
368
  }
 
369
 
 
370
  [result release];
 
371
  [upload release];
 
372
}
 
373
 
 
374
//=============================================================================
 
375
- (void)dealloc {
 
376
  [parameters_ release];
 
377
  [minidumpContents_ release];
 
378
  [super dealloc];
 
379
}
 
380
@end
 
381
 
 
382
//=============================================================================
 
383
int main(int argc, const char *argv[]) {
 
384
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
385
  NSLog(@"CrashSender launched.");
 
386
 
 
387
  // The expectation is that there will be one argument which is the path
 
388
  // to the configuration file
 
389
  if (argc != 2) {
 
390
    fprintf(stderr, "Missing configuration file\n");
 
391
    exit(1);
 
392
  }
 
393
 
 
394
  // Open the file before (potentially) switching to console user
 
395
  int configFile = open(argv[1], O_RDONLY, 0600);
 
396
  
 
397
  // we want to avoid a build-up of old config files even if they
 
398
  // have been incorrectly written by the framework
 
399
  unlink(argv[1]); 
 
400
 
 
401
  if (configFile == -1) {
 
402
    fprintf(stderr, "Unable to open configuration file: %s\n", argv[1]);
 
403
    exit(1);
 
404
  }
 
405
 
 
406
  Reporter *reporter = [[Reporter alloc] initWithConfigurationFD:configFile];
 
407
 
 
408
  // Gather the configuration data
 
409
  if (![reporter readConfigurationData]) {
 
410
    fprintf(stderr, "Unable to load configuration data from %s\n", argv[1]);
 
411
    exit(1);
 
412
  }
 
413
 
 
414
  // Read the minidump into memory before we (potentially) switch from the
 
415
  // root user
 
416
  if (![reporter readMinidumpData]) {
 
417
    fprintf(stderr, "Unable to read minidump data\n");
 
418
    exit(1);
 
419
  }
 
420
 
 
421
  // only submit a report if we have not recently crashed in the past
 
422
  BOOL shouldSubmitReport = [reporter shouldSubmitReport];
 
423
  BOOL okayToSend = NO;
 
424
  
 
425
  // ask user if we should send
 
426
  if (shouldSubmitReport) {
 
427
    okayToSend = [reporter askUserPermissionToSend:shouldSubmitReport];
 
428
  }
 
429
  
 
430
  // TODO(waylonis): What is the behavior when there is no console user?
 
431
  // Also, what about the global "Send statistics / metrics"?
 
432
 
 
433
  // If we're running as root, switch over to nobody
 
434
  if (getuid() == 0 || geteuid() == 0) {
 
435
    struct passwd *pw = getpwnam("nobody");
 
436
 
 
437
    // If we can't get a non-root uid, don't send the report
 
438
    if (!pw)
 
439
      exit(0);
 
440
 
 
441
    if (setgid(pw->pw_gid) == -1)
 
442
      exit(0);
 
443
 
 
444
    if (setuid(pw->pw_uid) == -1)
 
445
      exit(0);
 
446
  }
 
447
 
 
448
  if (okayToSend && shouldSubmitReport) {
 
449
    [reporter report];
 
450
  }
 
451
 
 
452
  // Cleanup
 
453
  [reporter release];
 
454
  [pool release];
 
455
 
 
456
  return 0;
 
457
}