mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-12-23 17:25:27 +00:00
Extract the uploader process from crash_report_sender
The aim is to separate the process itself from the view, to be able to reuse the process on the iOS platform. Review URL: http://breakpad.appspot.com/309002 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@853 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
da1c36d03e
commit
bf747d2dbb
|
@ -38,6 +38,7 @@
|
||||||
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; };
|
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; };
|
||||||
163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
|
163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
|
||||||
163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
|
163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; };
|
||||||
|
163202451443201300C4DBF5 /* uploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 163202441443201300C4DBF5 /* uploader.m */; };
|
||||||
3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; };
|
3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; };
|
||||||
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; };
|
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; };
|
||||||
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; };
|
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; };
|
||||||
|
@ -549,6 +550,8 @@
|
||||||
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||||
163201D41443019E00C4DBF5 /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigFile.h; path = crash_generation/ConfigFile.h; sourceTree = "<group>"; };
|
163201D41443019E00C4DBF5 /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigFile.h; path = crash_generation/ConfigFile.h; sourceTree = "<group>"; };
|
||||||
163201D51443019E00C4DBF5 /* ConfigFile.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = ConfigFile.mm; path = crash_generation/ConfigFile.mm; sourceTree = "<group>"; };
|
163201D51443019E00C4DBF5 /* ConfigFile.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = ConfigFile.mm; path = crash_generation/ConfigFile.mm; sourceTree = "<group>"; };
|
||||||
|
163202431443201300C4DBF5 /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = uploader.h; path = sender/uploader.h; sourceTree = "<group>"; };
|
||||||
|
163202441443201300C4DBF5 /* uploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = uploader.m; path = sender/uploader.m; sourceTree = "<group>"; };
|
||||||
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = "<group>"; };
|
32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = "<group>"; };
|
||||||
3329D4EC0FA16D820007BBC5 /* Breakpad.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Breakpad.xib; path = sender/Breakpad.xib; sourceTree = "<group>"; };
|
3329D4EC0FA16D820007BBC5 /* Breakpad.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Breakpad.xib; path = sender/Breakpad.xib; sourceTree = "<group>"; };
|
||||||
33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
|
@ -992,6 +995,8 @@
|
||||||
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
163202431443201300C4DBF5 /* uploader.h */,
|
||||||
|
163202441443201300C4DBF5 /* uploader.m */,
|
||||||
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
|
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
|
||||||
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
||||||
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
||||||
|
@ -1732,6 +1737,7 @@
|
||||||
F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */,
|
F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */,
|
||||||
F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */,
|
F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */,
|
||||||
F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */,
|
F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */,
|
||||||
|
163202451443201300C4DBF5 /* uploader.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,15 +34,9 @@
|
||||||
|
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
|
|
||||||
#include "client/mac/Framework/Breakpad.h"
|
#include "client/mac/sender/uploader.h"
|
||||||
#import "GTMDefines.h"
|
#import "GTMDefines.h"
|
||||||
|
|
||||||
#define kClientIdPreferenceKey @"clientid"
|
|
||||||
|
|
||||||
extern NSString *const kGoogleServerType;
|
|
||||||
extern NSString *const kSocorroServerType;
|
|
||||||
extern NSString *const kDefaultServerType;
|
|
||||||
|
|
||||||
// We're sublcassing NSTextField in order to override a particular
|
// We're sublcassing NSTextField in order to override a particular
|
||||||
// method (see the implementation) that lets us reject changes if they
|
// method (see the implementation) that lets us reject changes if they
|
||||||
// are longer than a particular length. Bindings would normally solve
|
// are longer than a particular length. Bindings would normally solve
|
||||||
|
@ -87,29 +81,12 @@ extern NSString *const kDefaultServerType;
|
||||||
NSString *countdownMessage_; // Message indicating time
|
NSString *countdownMessage_; // Message indicating time
|
||||||
// left for input.
|
// left for input.
|
||||||
@private
|
@private
|
||||||
int configFile_; // File descriptor for config file
|
|
||||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
|
||||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
|
||||||
NSData *logFileData_; // An NSdata for the tar,
|
|
||||||
// bz2'd log file.
|
|
||||||
NSTimeInterval remainingDialogTime_; // Keeps track of how long
|
NSTimeInterval remainingDialogTime_; // Keeps track of how long
|
||||||
// we have until we cancel
|
// we have until we cancel
|
||||||
// the dialog
|
// the dialog
|
||||||
NSTimer *messageTimer_; // Timer we use to update
|
NSTimer *messageTimer_; // Timer we use to update
|
||||||
// the dialog
|
// the dialog
|
||||||
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
Uploader* uploader_; // Uploader we use to send the data.
|
||||||
// server type name to a
|
|
||||||
// dictionary of server
|
|
||||||
// parameter names.
|
|
||||||
NSMutableDictionary *socorroDictionary_; // The dictionary for
|
|
||||||
// Socorro.
|
|
||||||
NSMutableDictionary *googleDictionary_; // The dictionary for
|
|
||||||
// Google.
|
|
||||||
NSMutableDictionary *extraServerVars_; // A dictionary containing
|
|
||||||
// extra key/value pairs
|
|
||||||
// that are uploaded to the
|
|
||||||
// crash server with the
|
|
||||||
// minidump.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops the modal panel with an NSAlertDefaultReturn value. This is the action
|
// Stops the modal panel with an NSAlertDefaultReturn value. This is the action
|
||||||
|
|
|
@ -41,17 +41,12 @@
|
||||||
|
|
||||||
|
|
||||||
#define kLastSubmission @"LastSubmission"
|
#define kLastSubmission @"LastSubmission"
|
||||||
const int kMinidumpFileLengthLimit = 800000;
|
|
||||||
const int kUserCommentsMaxLength = 1500;
|
const int kUserCommentsMaxLength = 1500;
|
||||||
const int kEmailMaxLength = 64;
|
const int kEmailMaxLength = 64;
|
||||||
|
|
||||||
#define kApplePrefsSyncExcludeAllKey \
|
#define kApplePrefsSyncExcludeAllKey \
|
||||||
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
||||||
|
|
||||||
NSString *const kGoogleServerType = @"google";
|
|
||||||
NSString *const kSocorroServerType = @"socorro";
|
|
||||||
NSString *const kDefaultServerType = @"google";
|
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
@interface NSView (ResizabilityExtentions)
|
@interface NSView (ResizabilityExtentions)
|
||||||
|
@ -160,18 +155,8 @@ NSString *const kDefaultServerType = @"google";
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
|
|
||||||
@interface Reporter(PrivateMethods)
|
@interface Reporter(PrivateMethods)
|
||||||
+ (uid_t)consoleUID;
|
- (id)initWithConfigFile:(const char *)configFile;
|
||||||
|
|
||||||
- (id)initWithConfigurationFD:(int)fd;
|
|
||||||
|
|
||||||
- (NSString *)readString;
|
|
||||||
- (NSData *)readData:(ssize_t)length;
|
|
||||||
|
|
||||||
- (BOOL)readConfigurationData;
|
|
||||||
- (BOOL)readMinidumpData;
|
|
||||||
- (BOOL)readLogFileData;
|
|
||||||
|
|
||||||
// Returns YES if it has been long enough since the last report that we should
|
// Returns YES if it has been long enough since the last report that we should
|
||||||
// submit a report for this crash.
|
// submit a report for this crash.
|
||||||
|
@ -221,30 +206,6 @@ NSString *const kDefaultServerType = @"google";
|
||||||
- (NSInteger)runModalWindow:(NSWindow*)window
|
- (NSInteger)runModalWindow:(NSWindow*)window
|
||||||
withTimeout:(NSTimeInterval)timeout;
|
withTimeout:(NSTimeInterval)timeout;
|
||||||
|
|
||||||
// Returns a unique client id (user-specific), creating a persistent
|
|
||||||
// one in the user defaults, if necessary.
|
|
||||||
- (NSString*)clientID;
|
|
||||||
|
|
||||||
// Returns a dictionary that can be used to map Breakpad parameter names to
|
|
||||||
// URL parameter names.
|
|
||||||
- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
|
|
||||||
|
|
||||||
// Helper method to set HTTP parameters based on server type. This is
|
|
||||||
// called right before the upload - crashParameters will contain, on exit,
|
|
||||||
// URL parameters that should be sent with the minidump.
|
|
||||||
- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
|
|
||||||
|
|
||||||
// Initialization helper to create dictionaries mapping Breakpad
|
|
||||||
// parameters to URL parameters
|
|
||||||
- (void)createServerParameterDictionaries;
|
|
||||||
|
|
||||||
// Accessor method for the URL parameter dictionary
|
|
||||||
- (NSMutableDictionary *)urlParameterDictionary;
|
|
||||||
|
|
||||||
// This method adds a key/value pair to the dictionary that
|
|
||||||
// will be uploaded to the crash server.
|
|
||||||
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
|
||||||
|
|
||||||
// This method is used to periodically update the UI with how many
|
// This method is used to periodically update the UI with how many
|
||||||
// seconds are left in the dialog display.
|
// seconds are left in the dialog display.
|
||||||
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
|
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
|
||||||
|
@ -255,291 +216,24 @@ NSString *const kDefaultServerType = @"google";
|
||||||
// in their comments/email.
|
// in their comments/email.
|
||||||
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
|
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
|
||||||
|
|
||||||
// Records the uploaded crash ID to the log file.
|
- (void)report;
|
||||||
- (void)logUploadWithID:(const char *)uploadID;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation Reporter
|
@implementation Reporter
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
+ (uid_t)consoleUID {
|
- (id)initWithConfigFile:(const char *)configFile {
|
||||||
SCDynamicStoreRef store =
|
|
||||||
SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Reporter"), NULL, NULL);
|
|
||||||
uid_t uid = -2; // Default to "nobody"
|
|
||||||
if (store) {
|
|
||||||
CFStringRef user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL);
|
|
||||||
|
|
||||||
if (user)
|
|
||||||
CFRelease(user);
|
|
||||||
else
|
|
||||||
uid = -2;
|
|
||||||
|
|
||||||
CFRelease(store);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (id)initWithConfigurationFD:(int)fd {
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
configFile_ = fd;
|
|
||||||
remainingDialogTime_ = 0;
|
remainingDialogTime_ = 0;
|
||||||
|
uploader_ = [[Uploader alloc] initWithConfigFile:configFile];
|
||||||
|
if (!uploader_) {
|
||||||
|
[self release];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because the reporter is embedded in the framework (and many copies
|
|
||||||
// of the framework may exist) its not completely certain that the OS
|
|
||||||
// will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
|
|
||||||
// Info.plist. To make sure, also set the key directly if needed.
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
|
|
||||||
[ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self createServerParameterDictionaries];
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (NSString *)readString {
|
|
||||||
NSMutableString *str = [NSMutableString stringWithCapacity:32];
|
|
||||||
char ch[2] = { 0 };
|
|
||||||
|
|
||||||
while (read(configFile_, &ch[0], 1) == 1) {
|
|
||||||
if (ch[0] == '\n') {
|
|
||||||
// Break if this is the first newline after reading some other string
|
|
||||||
// data.
|
|
||||||
if ([str length])
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
[str appendString:[NSString stringWithUTF8String:ch]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (NSData *)readData:(ssize_t)length {
|
|
||||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
|
||||||
char *bytes = (char *)[data bytes];
|
|
||||||
|
|
||||||
if (read(configFile_, bytes, length) != length)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (BOOL)readConfigurationData {
|
|
||||||
parameters_ = [[NSMutableDictionary alloc] init];
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
NSString *key = [self readString];
|
|
||||||
|
|
||||||
if (![key length])
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Read the data. Try to convert to a UTF-8 string, or just save
|
|
||||||
// the data
|
|
||||||
NSString *lenStr = [self readString];
|
|
||||||
ssize_t len = [lenStr intValue];
|
|
||||||
NSData *data = [self readData:len];
|
|
||||||
id value = [[NSString alloc] initWithData:data
|
|
||||||
encoding:NSUTF8StringEncoding];
|
|
||||||
|
|
||||||
// If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
|
|
||||||
// that indicates that it should be uploaded to the server along
|
|
||||||
// with the minidump, so we treat it specially.
|
|
||||||
if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
|
|
||||||
NSString *urlParameterKey =
|
|
||||||
[key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
|
|
||||||
if ([urlParameterKey length]) {
|
|
||||||
if (value) {
|
|
||||||
[self addServerParameter:value
|
|
||||||
forKey:urlParameterKey];
|
|
||||||
} else {
|
|
||||||
[self addServerParameter:data
|
|
||||||
forKey:urlParameterKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[parameters_ setObject:(value ? value : data) forKey:key];
|
|
||||||
}
|
|
||||||
[value release];
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a unique client ID based on this host's MAC address
|
|
||||||
// then add a key/value pair for it
|
|
||||||
NSString *clientID = [self clientID];
|
|
||||||
[parameters_ setObject:clientID forKey:@"guid"];
|
|
||||||
|
|
||||||
close(configFile_);
|
|
||||||
configFile_ = -1;
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per user per machine
|
|
||||||
- (NSString *)clientID {
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
|
|
||||||
if (crashClientID) {
|
|
||||||
return crashClientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if we have no client id, generate one!
|
|
||||||
srandom((int)[[NSDate date] timeIntervalSince1970]);
|
|
||||||
long clientId1 = random();
|
|
||||||
long clientId2 = random();
|
|
||||||
long clientId3 = random();
|
|
||||||
crashClientID = [NSString stringWithFormat:@"%x%x%x",
|
|
||||||
clientId1, clientId2, clientId3];
|
|
||||||
|
|
||||||
[ud setObject:crashClientID forKey:kClientIdPreferenceKey];
|
|
||||||
[ud synchronize];
|
|
||||||
return crashClientID;
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (BOOL)readLogFileData {
|
|
||||||
unsigned int logFileCounter = 0;
|
|
||||||
|
|
||||||
NSString *logPath;
|
|
||||||
size_t logFileTailSize =
|
|
||||||
[[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
|
|
||||||
|
|
||||||
NSMutableArray *logFilenames; // An array of NSString, one per log file
|
|
||||||
logFilenames = [[NSMutableArray alloc] init];
|
|
||||||
|
|
||||||
char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
|
|
||||||
char *tmpDir = mkdtemp(tmpDirTemplate);
|
|
||||||
|
|
||||||
// Construct key names for the keys we expect to contain log file paths
|
|
||||||
for(logFileCounter = 0;; logFileCounter++) {
|
|
||||||
NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
|
|
||||||
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
|
||||||
logFileCounter];
|
|
||||||
|
|
||||||
logPath = [parameters_ objectForKey:logFileKey];
|
|
||||||
|
|
||||||
// They should all be consecutive, so if we don't find one, assume
|
|
||||||
// we're done
|
|
||||||
|
|
||||||
if (!logPath) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
|
|
||||||
|
|
||||||
if (entireLogFile == nil) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSRange fileRange;
|
|
||||||
|
|
||||||
// Truncate the log file, only if necessary
|
|
||||||
|
|
||||||
if ([entireLogFile length] <= logFileTailSize) {
|
|
||||||
fileRange = NSMakeRange(0, [entireLogFile length]);
|
|
||||||
} else {
|
|
||||||
fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
|
|
||||||
logFileTailSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
char tmpFilenameTemplate[100];
|
|
||||||
|
|
||||||
// Generate a template based on the log filename
|
|
||||||
sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
|
|
||||||
[[logPath lastPathComponent] fileSystemRepresentation]);
|
|
||||||
|
|
||||||
char *tmpFile = mktemp(tmpFilenameTemplate);
|
|
||||||
|
|
||||||
NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
|
|
||||||
NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
|
|
||||||
[logSubdata writeToFile:tmpFileString atomically:NO];
|
|
||||||
|
|
||||||
[logFilenames addObject:[tmpFileString lastPathComponent]];
|
|
||||||
[entireLogFile release];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([logFilenames count] == 0) {
|
|
||||||
[logFilenames release];
|
|
||||||
logFileData_ = nil;
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// now, bzip all files into one
|
|
||||||
NSTask *tarTask = [[NSTask alloc] init];
|
|
||||||
|
|
||||||
[tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
|
|
||||||
[tarTask setLaunchPath:@"/usr/bin/tar"];
|
|
||||||
|
|
||||||
NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
|
|
||||||
@"log.tar.bz2",nil];
|
|
||||||
[bzipArgs addObjectsFromArray:logFilenames];
|
|
||||||
|
|
||||||
[logFilenames release];
|
|
||||||
|
|
||||||
[tarTask setArguments:bzipArgs];
|
|
||||||
[tarTask launch];
|
|
||||||
[tarTask waitUntilExit];
|
|
||||||
[tarTask release];
|
|
||||||
|
|
||||||
NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
|
|
||||||
logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
|
|
||||||
if (logFileData_ == nil) {
|
|
||||||
GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (BOOL)readMinidumpData {
|
|
||||||
NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
|
||||||
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
|
||||||
|
|
||||||
if (![minidumpID length])
|
|
||||||
return NO;
|
|
||||||
|
|
||||||
NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
|
|
||||||
path = [path stringByAppendingPathExtension:@"dmp"];
|
|
||||||
|
|
||||||
// check the size of the minidump and limit it to a reasonable size
|
|
||||||
// before attempting to load into memory and upload
|
|
||||||
const char *fileName = [path fileSystemRepresentation];
|
|
||||||
struct stat fileStatus;
|
|
||||||
|
|
||||||
BOOL success = YES;
|
|
||||||
|
|
||||||
if (!stat(fileName, &fileStatus)) {
|
|
||||||
if (fileStatus.st_size > kMinidumpFileLengthLimit) {
|
|
||||||
fprintf(stderr, "Breakpad Reporter: minidump file too large " \
|
|
||||||
"to upload : %d\n", (int)fileStatus.st_size);
|
|
||||||
success = NO;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Breakpad Reporter: unable to determine minidump " \
|
|
||||||
"file length\n");
|
|
||||||
success = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
|
|
||||||
success = ([minidumpContents_ length] ? YES : NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
// something wrong with the minidump file -- delete it
|
|
||||||
unlink(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (BOOL)askUserPermissionToSend {
|
- (BOOL)askUserPermissionToSend {
|
||||||
// Initialize Cocoa, needed to display the alert
|
// Initialize Cocoa, needed to display the alert
|
||||||
|
@ -560,12 +254,14 @@ NSString *const kDefaultServerType = @"google";
|
||||||
|
|
||||||
buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout];
|
buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout];
|
||||||
|
|
||||||
// Extract info from the user into the parameters_ dictionary
|
// Extract info from the user into the uploader_.
|
||||||
if ([self commentsValue]) {
|
if ([self commentsValue]) {
|
||||||
[parameters_ setObject:[self commentsValue] forKey:@BREAKPAD_COMMENTS];
|
[[uploader_ parameters] setObject:[self commentsValue]
|
||||||
|
forKey:@BREAKPAD_COMMENTS];
|
||||||
}
|
}
|
||||||
if ([self emailValue]) {
|
if ([self emailValue]) {
|
||||||
[parameters_ setObject:[self emailValue] forKey:@BREAKPAD_EMAIL];
|
[[uploader_ parameters] setObject:[self emailValue]
|
||||||
|
forKey:@BREAKPAD_EMAIL];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create an alert panel to tell the user something happened
|
// Create an alert panel to tell the user something happened
|
||||||
|
@ -804,9 +500,9 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (BOOL)reportIntervalElapsed {
|
- (BOOL)reportIntervalElapsed {
|
||||||
float interval = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL]
|
float interval = [[[uploader_ parameters]
|
||||||
floatValue];
|
objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue];
|
||||||
NSString *program = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
NSString *program = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
NSMutableDictionary *programDict =
|
NSMutableDictionary *programDict =
|
||||||
[NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
|
[NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
|
||||||
|
@ -831,29 +527,30 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isOnDemand {
|
- (BOOL)isOnDemand {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND]
|
return [[[uploader_ parameters] objectForKey:@BREAKPAD_ON_DEMAND]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)shouldSubmitSilently {
|
- (BOOL)shouldSubmitSilently {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
return [[[uploader_ parameters] objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)shouldRequestComments {
|
- (BOOL)shouldRequestComments {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS]
|
return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_COMMENTS]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)shouldRequestEmail {
|
- (BOOL)shouldRequestEmail {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_REQUEST_EMAIL]
|
return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_EMAIL]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)shortDialogMessage {
|
- (NSString*)shortDialogMessage {
|
||||||
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
NSString *displayName =
|
||||||
|
[[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
if (![displayName length])
|
if (![displayName length])
|
||||||
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
if ([self isOnDemand]) {
|
if ([self isOnDemand]) {
|
||||||
return [NSString
|
return [NSString
|
||||||
|
@ -867,11 +564,12 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)explanatoryDialogText {
|
- (NSString*)explanatoryDialogText {
|
||||||
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
NSString *displayName =
|
||||||
|
[[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
if (![displayName length])
|
if (![displayName length])
|
||||||
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
NSString *vendor = [[uploader_ parameters] objectForKey:@BREAKPAD_VENDOR];
|
||||||
if (![vendor length])
|
if (![vendor length])
|
||||||
vendor = @"unknown vendor";
|
vendor = @"unknown vendor";
|
||||||
|
|
||||||
|
@ -888,8 +586,8 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
|
|
||||||
- (NSTimeInterval)messageTimeout {
|
- (NSTimeInterval)messageTimeout {
|
||||||
// Get the timeout value for the notification.
|
// Get the timeout value for the notification.
|
||||||
NSTimeInterval timeout = [[parameters_ objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]
|
NSTimeInterval timeout = [[[uploader_ parameters]
|
||||||
floatValue];
|
objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] floatValue];
|
||||||
// Require a timeout of at least a minute (except 0, which means no timeout).
|
// Require a timeout of at least a minute (except 0, which means no timeout).
|
||||||
if (timeout > 0.001 && timeout < 60.0) {
|
if (timeout > 0.001 && timeout < 60.0) {
|
||||||
timeout = 60.0;
|
timeout = 60.0;
|
||||||
|
@ -897,194 +595,13 @@ doCommandBySelector:(SEL)commandSelector {
|
||||||
return timeout;
|
return timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)createServerParameterDictionaries {
|
|
||||||
serverDictionary_ = [[NSMutableDictionary alloc] init];
|
|
||||||
socorroDictionary_ = [[NSMutableDictionary alloc] init];
|
|
||||||
googleDictionary_ = [[NSMutableDictionary alloc] init];
|
|
||||||
extraServerVars_ = [[NSMutableDictionary alloc] init];
|
|
||||||
|
|
||||||
[serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
|
|
||||||
[serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
|
|
||||||
|
|
||||||
[googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
|
|
||||||
[googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
|
|
||||||
[googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
|
|
||||||
[googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
|
|
||||||
[googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
|
|
||||||
|
|
||||||
[socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
|
|
||||||
[socorroDictionary_ setObject:@"CrashTime"
|
|
||||||
forKey:@BREAKPAD_PROCESS_CRASH_TIME];
|
|
||||||
[socorroDictionary_ setObject:@"StartupTime"
|
|
||||||
forKey:@BREAKPAD_PROCESS_START_TIME];
|
|
||||||
[socorroDictionary_ setObject:@"Version"
|
|
||||||
forKey:@BREAKPAD_VERSION];
|
|
||||||
[socorroDictionary_ setObject:@"ProductName"
|
|
||||||
forKey:@BREAKPAD_PRODUCT];
|
|
||||||
[socorroDictionary_ setObject:@"Email"
|
|
||||||
forKey:@BREAKPAD_EMAIL];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
|
|
||||||
if (serverType == nil || [serverType length] == 0) {
|
|
||||||
return [serverDictionary_ objectForKey:kDefaultServerType];
|
|
||||||
}
|
|
||||||
return [serverDictionary_ objectForKey:serverType];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSMutableDictionary *)urlParameterDictionary {
|
|
||||||
NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
|
|
||||||
return [self dictionaryForServerType:serverType];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
|
|
||||||
NSDictionary *urlParameterNames = [self urlParameterDictionary];
|
|
||||||
|
|
||||||
id key;
|
|
||||||
NSEnumerator *enumerator = [parameters_ keyEnumerator];
|
|
||||||
|
|
||||||
while ((key = [enumerator nextObject])) {
|
|
||||||
// The key from parameters_ corresponds to a key in
|
|
||||||
// urlParameterNames. The value in parameters_ gets stored in
|
|
||||||
// crashParameters with a key that is the value in
|
|
||||||
// urlParameterNames.
|
|
||||||
|
|
||||||
// For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
|
|
||||||
// urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
|
|
||||||
// URL parameter becomes [pname => "FOOBAR"].
|
|
||||||
NSString *breakpadParameterName = (NSString *)key;
|
|
||||||
NSString *urlParameter = [urlParameterNames
|
|
||||||
objectForKey:breakpadParameterName];
|
|
||||||
if (urlParameter) {
|
|
||||||
[crashParameters setObject:[parameters_ objectForKey:key]
|
|
||||||
forKey:urlParameter];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, add the parameters that were added by the application.
|
|
||||||
enumerator = [extraServerVars_ keyEnumerator];
|
|
||||||
|
|
||||||
while ((key = [enumerator nextObject])) {
|
|
||||||
NSString *urlParameterName = (NSString *)key;
|
|
||||||
NSString *urlParameterValue =
|
|
||||||
[extraServerVars_ objectForKey:urlParameterName];
|
|
||||||
[crashParameters setObject:urlParameterValue
|
|
||||||
forKey:urlParameterName];
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)addServerParameter:(id)value forKey:(NSString *)key {
|
|
||||||
[extraServerVars_ setObject:value forKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
|
||||||
- (void)report {
|
- (void)report {
|
||||||
NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
|
[uploader_ report];
|
||||||
HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
|
|
||||||
NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
|
|
||||||
|
|
||||||
if (![self populateServerDictionary:uploadParameters]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[upload setParameters:uploadParameters];
|
|
||||||
|
|
||||||
// Add minidump file
|
|
||||||
if (minidumpContents_) {
|
|
||||||
[upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
|
|
||||||
|
|
||||||
// Send it
|
|
||||||
NSError *error = nil;
|
|
||||||
NSData *data = [upload send:&error];
|
|
||||||
NSString *result = [[NSString alloc] initWithData:data
|
|
||||||
encoding:NSUTF8StringEncoding];
|
|
||||||
const char *reportID = "ERR";
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
fprintf(stderr, "Breakpad Reporter: Send Error: %s\n",
|
|
||||||
[[error description] UTF8String]);
|
|
||||||
} else {
|
|
||||||
NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
|
||||||
reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
|
|
||||||
[self logUploadWithID:reportID];
|
|
||||||
}
|
|
||||||
|
|
||||||
// rename the minidump file according to the id returned from the server
|
|
||||||
NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
|
||||||
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
|
||||||
|
|
||||||
NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
|
|
||||||
minidumpDir, minidumpID];
|
|
||||||
NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
|
|
||||||
minidumpDir, reportID];
|
|
||||||
|
|
||||||
const char *src = [srcString fileSystemRepresentation];
|
|
||||||
const char *dest = [destString fileSystemRepresentation];
|
|
||||||
|
|
||||||
if (rename(src, dest) == 0) {
|
|
||||||
GTMLoggerInfo(@"Breakpad Reporter: Renamed %s to %s after successful " \
|
|
||||||
"upload",src, dest);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// can't rename - don't worry - it's not important for users
|
|
||||||
GTMLoggerDebug(@"Breakpad Reporter: successful upload report ID = %s\n",
|
|
||||||
reportID );
|
|
||||||
}
|
|
||||||
[result release];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logFileData_) {
|
|
||||||
HTTPMultipartUpload *logUpload = [[HTTPMultipartUpload alloc] initWithURL:url];
|
|
||||||
|
|
||||||
[uploadParameters setObject:@"log" forKey:@"type"];
|
|
||||||
[logUpload setParameters:uploadParameters];
|
|
||||||
[logUpload addFileContents:logFileData_ name:@"log"];
|
|
||||||
|
|
||||||
NSError *error = nil;
|
|
||||||
NSData *data = [logUpload send:&error];
|
|
||||||
NSString *result = [[NSString alloc] initWithData:data
|
|
||||||
encoding:NSUTF8StringEncoding];
|
|
||||||
[result release];
|
|
||||||
[logUpload release];
|
|
||||||
}
|
|
||||||
|
|
||||||
[upload release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)logUploadWithID:(const char *)uploadID {
|
|
||||||
NSString *minidumpDir =
|
|
||||||
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
|
||||||
NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
|
|
||||||
minidumpDir, kReporterLogFilename];
|
|
||||||
NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
|
|
||||||
[[NSDate date] timeIntervalSince1970], uploadID];
|
|
||||||
NSData *logData = [logLine dataUsingEncoding:kCFStringEncodingUTF8];
|
|
||||||
|
|
||||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
||||||
if ([fileManager fileExistsAtPath:logFilePath]) {
|
|
||||||
NSFileHandle *logFileHandle =
|
|
||||||
[NSFileHandle fileHandleForWritingAtPath:logFilePath];
|
|
||||||
[logFileHandle seekToEndOfFile];
|
|
||||||
[logFileHandle writeData:logData];
|
|
||||||
[logFileHandle closeFile];
|
|
||||||
} else {
|
|
||||||
[fileManager createFileAtPath:logFilePath
|
|
||||||
contents:logData
|
|
||||||
attributes:nil];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
[parameters_ release];
|
[uploader_ release];
|
||||||
[minidumpContents_ release];
|
|
||||||
[logFileData_ release];
|
|
||||||
[googleDictionary_ release];
|
|
||||||
[socorroDictionary_ release];
|
|
||||||
[serverDictionary_ release];
|
|
||||||
[extraServerVars_ release];
|
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,40 +692,12 @@ int main(int argc, const char *argv[]) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the file before (potentially) switching to console user
|
Reporter *reporter = [[Reporter alloc] initWithConfigFile:argv[1]];
|
||||||
int configFile = open(argv[1], O_RDONLY, 0600);
|
if (!reporter) {
|
||||||
|
GTMLoggerDebug(@"reporter initialization failed");
|
||||||
if (configFile == -1) {
|
|
||||||
GTMLoggerDebug(@"Couldn't open config file %s - %s",
|
|
||||||
argv[1],
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
// we want to avoid a build-up of old config files even if they
|
|
||||||
// have been incorrectly written by the framework
|
|
||||||
unlink(argv[1]);
|
|
||||||
|
|
||||||
if (configFile == -1) {
|
|
||||||
GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
|
|
||||||
argv[1],
|
|
||||||
strerror(errno));
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Reporter *reporter = [[Reporter alloc] initWithConfigurationFD:configFile];
|
|
||||||
|
|
||||||
// Gather the configuration data
|
|
||||||
if (![reporter readConfigurationData]) {
|
|
||||||
GTMLoggerDebug(@"reporter readConfigurationData failed");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the minidump into memory before we (potentially) switch from the
|
|
||||||
// root user
|
|
||||||
[reporter readMinidumpData];
|
|
||||||
|
|
||||||
[reporter readLogFileData];
|
|
||||||
|
|
||||||
// only submit a report if we have not recently crashed in the past
|
// only submit a report if we have not recently crashed in the past
|
||||||
BOOL shouldSubmitReport = [reporter reportIntervalElapsed];
|
BOOL shouldSubmitReport = [reporter reportIntervalElapsed];
|
||||||
BOOL okayToSend = NO;
|
BOOL okayToSend = NO;
|
||||||
|
|
74
src/client/mac/sender/uploader.h
Normal file
74
src/client/mac/sender/uploader.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) 2011, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
// This component uses the HTTPMultipartUpload of the breakpad project to send
|
||||||
|
// the minidump and associated data to the crash reporting servers.
|
||||||
|
// It will perform throttling based on the parameters passed to it and will
|
||||||
|
// prompt the user to send the minidump.
|
||||||
|
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#include "client/mac/Framework/Breakpad.h"
|
||||||
|
#import "common/mac/GTMDefines.h"
|
||||||
|
|
||||||
|
#define kClientIdPreferenceKey @"clientid"
|
||||||
|
|
||||||
|
extern NSString *const kGoogleServerType;
|
||||||
|
extern NSString *const kSocorroServerType;
|
||||||
|
extern NSString *const kDefaultServerType;
|
||||||
|
|
||||||
|
@interface Uploader : NSObject {
|
||||||
|
@private
|
||||||
|
int configFile_; // File descriptor for config file
|
||||||
|
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||||
|
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||||
|
NSData *logFileData_; // An NSdata for the tar,
|
||||||
|
// bz2'd log file.
|
||||||
|
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
||||||
|
// server type name to a
|
||||||
|
// dictionary of server
|
||||||
|
// parameter names.
|
||||||
|
NSMutableDictionary *socorroDictionary_; // The dictionary for
|
||||||
|
// Socorro.
|
||||||
|
NSMutableDictionary *googleDictionary_; // The dictionary for
|
||||||
|
// Google.
|
||||||
|
NSMutableDictionary *extraServerVars_; // A dictionary containing
|
||||||
|
// extra key/value pairs
|
||||||
|
// that are uploaded to the
|
||||||
|
// crash server with the
|
||||||
|
// minidump.
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithConfigFile:(const char *)configFile;
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)parameters;
|
||||||
|
|
||||||
|
- (void)report;
|
||||||
|
|
||||||
|
@end
|
579
src/client/mac/sender/uploader.m
Normal file
579
src/client/mac/sender/uploader.m
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
// Copyright (c) 2011, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#import <pwd.h>
|
||||||
|
#import <sys/stat.h>
|
||||||
|
#import <unistd.h>
|
||||||
|
|
||||||
|
#import <SystemConfiguration/SystemConfiguration.h>
|
||||||
|
|
||||||
|
#import "common/mac/HTTPMultipartUpload.h"
|
||||||
|
|
||||||
|
#import "client/mac/sender/uploader.h"
|
||||||
|
#import "common/mac/GTMLogger.h"
|
||||||
|
|
||||||
|
|
||||||
|
const int kMinidumpFileLengthLimit = 800000;
|
||||||
|
|
||||||
|
#define kApplePrefsSyncExcludeAllKey \
|
||||||
|
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
||||||
|
|
||||||
|
NSString *const kGoogleServerType = @"google";
|
||||||
|
NSString *const kSocorroServerType = @"socorro";
|
||||||
|
NSString *const kDefaultServerType = @"google";
|
||||||
|
|
||||||
|
#define GTMLoggerDebug NSLog
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@interface Uploader(PrivateMethods)
|
||||||
|
- (NSString *)readString;
|
||||||
|
- (NSData *)readData:(ssize_t)length;
|
||||||
|
|
||||||
|
- (BOOL)readConfigurationData;
|
||||||
|
- (BOOL)readMinidumpData;
|
||||||
|
- (BOOL)readLogFileData;
|
||||||
|
|
||||||
|
// Returns a unique client id (user-specific), creating a persistent
|
||||||
|
// one in the user defaults, if necessary.
|
||||||
|
- (NSString*)clientID;
|
||||||
|
|
||||||
|
// Returns a dictionary that can be used to map Breakpad parameter names to
|
||||||
|
// URL parameter names.
|
||||||
|
- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
|
||||||
|
|
||||||
|
// Helper method to set HTTP parameters based on server type. This is
|
||||||
|
// called right before the upload - crashParameters will contain, on exit,
|
||||||
|
// URL parameters that should be sent with the minidump.
|
||||||
|
- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
|
||||||
|
|
||||||
|
// Initialization helper to create dictionaries mapping Breakpad
|
||||||
|
// parameters to URL parameters
|
||||||
|
- (void)createServerParameterDictionaries;
|
||||||
|
|
||||||
|
// Accessor method for the URL parameter dictionary
|
||||||
|
- (NSMutableDictionary *)urlParameterDictionary;
|
||||||
|
|
||||||
|
// This method adds a key/value pair to the dictionary that
|
||||||
|
// will be uploaded to the crash server.
|
||||||
|
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
||||||
|
|
||||||
|
// Records the uploaded crash ID to the log file.
|
||||||
|
- (void)logUploadWithID:(const char *)uploadID;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Uploader
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (id)initWithConfigFile:(const char *)configFile {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
|
||||||
|
configFile_ = open(configFile, O_RDONLY, 0600);
|
||||||
|
if (configFile_ == -1) {
|
||||||
|
GTMLoggerDebug(@"Couldn't open config file %s - %s",
|
||||||
|
configFile,
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to avoid a build-up of old config files even if they
|
||||||
|
// have been incorrectly written by the framework
|
||||||
|
if (unlink(configFile)) {
|
||||||
|
GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
|
||||||
|
configFile,
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configFile_ == -1) {
|
||||||
|
[self release];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because the reporter is embedded in the framework (and many copies
|
||||||
|
// of the framework may exist) its not completely certain that the OS
|
||||||
|
// will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
|
||||||
|
// Info.plist. To make sure, also set the key directly if needed.
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
|
||||||
|
[ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self createServerParameterDictionaries];
|
||||||
|
|
||||||
|
if (![self readConfigurationData]) {
|
||||||
|
GTMLoggerDebug(@"uploader readConfigurationData failed");
|
||||||
|
[self release];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the minidump into memory.
|
||||||
|
[self readMinidumpData];
|
||||||
|
[self readLogFileData];
|
||||||
|
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (NSString *)readString {
|
||||||
|
NSMutableString *str = [NSMutableString stringWithCapacity:32];
|
||||||
|
char ch[2] = { 0 };
|
||||||
|
|
||||||
|
while (read(configFile_, &ch[0], 1) == 1) {
|
||||||
|
if (ch[0] == '\n') {
|
||||||
|
// Break if this is the first newline after reading some other string
|
||||||
|
// data.
|
||||||
|
if ([str length])
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
[str appendString:[NSString stringWithUTF8String:ch]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (NSData *)readData:(ssize_t)length {
|
||||||
|
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||||
|
char *bytes = (char *)[data bytes];
|
||||||
|
|
||||||
|
if (read(configFile_, bytes, length) != length)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (BOOL)readConfigurationData {
|
||||||
|
parameters_ = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
NSString *key = [self readString];
|
||||||
|
|
||||||
|
if (![key length])
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Read the data. Try to convert to a UTF-8 string, or just save
|
||||||
|
// the data
|
||||||
|
NSString *lenStr = [self readString];
|
||||||
|
ssize_t len = [lenStr intValue];
|
||||||
|
NSData *data = [self readData:len];
|
||||||
|
id value = [[NSString alloc] initWithData:data
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
|
// If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
|
||||||
|
// that indicates that it should be uploaded to the server along
|
||||||
|
// with the minidump, so we treat it specially.
|
||||||
|
if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
|
||||||
|
NSString *urlParameterKey =
|
||||||
|
[key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
|
||||||
|
if ([urlParameterKey length]) {
|
||||||
|
if (value) {
|
||||||
|
[self addServerParameter:value
|
||||||
|
forKey:urlParameterKey];
|
||||||
|
} else {
|
||||||
|
[self addServerParameter:data
|
||||||
|
forKey:urlParameterKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[parameters_ setObject:(value ? value : data) forKey:key];
|
||||||
|
}
|
||||||
|
[value release];
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a unique client ID based on this host's MAC address
|
||||||
|
// then add a key/value pair for it
|
||||||
|
NSString *clientID = [self clientID];
|
||||||
|
[parameters_ setObject:clientID forKey:@"guid"];
|
||||||
|
|
||||||
|
close(configFile_);
|
||||||
|
configFile_ = -1;
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per user per machine
|
||||||
|
- (NSString *)clientID {
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
|
||||||
|
if (crashClientID) {
|
||||||
|
return crashClientID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if we have no client id, generate one!
|
||||||
|
srandom((int)[[NSDate date] timeIntervalSince1970]);
|
||||||
|
long clientId1 = random();
|
||||||
|
long clientId2 = random();
|
||||||
|
long clientId3 = random();
|
||||||
|
crashClientID = [NSString stringWithFormat:@"%x%x%x",
|
||||||
|
clientId1, clientId2, clientId3];
|
||||||
|
|
||||||
|
[ud setObject:crashClientID forKey:kClientIdPreferenceKey];
|
||||||
|
[ud synchronize];
|
||||||
|
return crashClientID;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (BOOL)readLogFileData {
|
||||||
|
unsigned int logFileCounter = 0;
|
||||||
|
|
||||||
|
NSString *logPath;
|
||||||
|
size_t logFileTailSize =
|
||||||
|
[[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
|
||||||
|
|
||||||
|
NSMutableArray *logFilenames; // An array of NSString, one per log file
|
||||||
|
logFilenames = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
|
||||||
|
char *tmpDir = mkdtemp(tmpDirTemplate);
|
||||||
|
|
||||||
|
// Construct key names for the keys we expect to contain log file paths
|
||||||
|
for(logFileCounter = 0;; logFileCounter++) {
|
||||||
|
NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
|
||||||
|
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
||||||
|
logFileCounter];
|
||||||
|
|
||||||
|
logPath = [parameters_ objectForKey:logFileKey];
|
||||||
|
|
||||||
|
// They should all be consecutive, so if we don't find one, assume
|
||||||
|
// we're done
|
||||||
|
|
||||||
|
if (!logPath) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
|
||||||
|
|
||||||
|
if (entireLogFile == nil) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSRange fileRange;
|
||||||
|
|
||||||
|
// Truncate the log file, only if necessary
|
||||||
|
|
||||||
|
if ([entireLogFile length] <= logFileTailSize) {
|
||||||
|
fileRange = NSMakeRange(0, [entireLogFile length]);
|
||||||
|
} else {
|
||||||
|
fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
|
||||||
|
logFileTailSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
char tmpFilenameTemplate[100];
|
||||||
|
|
||||||
|
// Generate a template based on the log filename
|
||||||
|
sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
|
||||||
|
[[logPath lastPathComponent] fileSystemRepresentation]);
|
||||||
|
|
||||||
|
char *tmpFile = mktemp(tmpFilenameTemplate);
|
||||||
|
|
||||||
|
NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
|
||||||
|
NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
|
||||||
|
[logSubdata writeToFile:tmpFileString atomically:NO];
|
||||||
|
|
||||||
|
[logFilenames addObject:[tmpFileString lastPathComponent]];
|
||||||
|
[entireLogFile release];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([logFilenames count] == 0) {
|
||||||
|
[logFilenames release];
|
||||||
|
logFileData_ = nil;
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, bzip all files into one
|
||||||
|
NSTask *tarTask = [[NSTask alloc] init];
|
||||||
|
|
||||||
|
[tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
|
||||||
|
[tarTask setLaunchPath:@"/usr/bin/tar"];
|
||||||
|
|
||||||
|
NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
|
||||||
|
@"log.tar.bz2",nil];
|
||||||
|
[bzipArgs addObjectsFromArray:logFilenames];
|
||||||
|
|
||||||
|
[logFilenames release];
|
||||||
|
|
||||||
|
[tarTask setArguments:bzipArgs];
|
||||||
|
[tarTask launch];
|
||||||
|
[tarTask waitUntilExit];
|
||||||
|
[tarTask release];
|
||||||
|
|
||||||
|
NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
|
||||||
|
logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
|
||||||
|
if (logFileData_ == nil) {
|
||||||
|
GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (BOOL)readMinidumpData {
|
||||||
|
NSString *minidumpDir =
|
||||||
|
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||||
|
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
||||||
|
|
||||||
|
if (![minidumpID length])
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
|
||||||
|
path = [path stringByAppendingPathExtension:@"dmp"];
|
||||||
|
|
||||||
|
// check the size of the minidump and limit it to a reasonable size
|
||||||
|
// before attempting to load into memory and upload
|
||||||
|
const char *fileName = [path fileSystemRepresentation];
|
||||||
|
struct stat fileStatus;
|
||||||
|
|
||||||
|
BOOL success = YES;
|
||||||
|
|
||||||
|
if (!stat(fileName, &fileStatus)) {
|
||||||
|
if (fileStatus.st_size > kMinidumpFileLengthLimit) {
|
||||||
|
fprintf(stderr, "Breakpad Uploader: minidump file too large " \
|
||||||
|
"to upload : %d\n", (int)fileStatus.st_size);
|
||||||
|
success = NO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
|
||||||
|
"file length\n");
|
||||||
|
success = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
|
||||||
|
success = ([minidumpContents_ length] ? YES : NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
// something wrong with the minidump file -- delete it
|
||||||
|
unlink(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
- (void)createServerParameterDictionaries {
|
||||||
|
serverDictionary_ = [[NSMutableDictionary alloc] init];
|
||||||
|
socorroDictionary_ = [[NSMutableDictionary alloc] init];
|
||||||
|
googleDictionary_ = [[NSMutableDictionary alloc] init];
|
||||||
|
extraServerVars_ = [[NSMutableDictionary alloc] init];
|
||||||
|
|
||||||
|
[serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
|
||||||
|
[serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
|
||||||
|
|
||||||
|
[googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
|
||||||
|
[googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
|
||||||
|
[googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
|
||||||
|
[googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
|
||||||
|
[googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
|
||||||
|
|
||||||
|
[socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
|
||||||
|
[socorroDictionary_ setObject:@"CrashTime"
|
||||||
|
forKey:@BREAKPAD_PROCESS_CRASH_TIME];
|
||||||
|
[socorroDictionary_ setObject:@"StartupTime"
|
||||||
|
forKey:@BREAKPAD_PROCESS_START_TIME];
|
||||||
|
[socorroDictionary_ setObject:@"Version"
|
||||||
|
forKey:@BREAKPAD_VERSION];
|
||||||
|
[socorroDictionary_ setObject:@"ProductName"
|
||||||
|
forKey:@BREAKPAD_PRODUCT];
|
||||||
|
[socorroDictionary_ setObject:@"Email"
|
||||||
|
forKey:@BREAKPAD_EMAIL];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
|
||||||
|
if (serverType == nil || [serverType length] == 0) {
|
||||||
|
return [serverDictionary_ objectForKey:kDefaultServerType];
|
||||||
|
}
|
||||||
|
return [serverDictionary_ objectForKey:serverType];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)urlParameterDictionary {
|
||||||
|
NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
|
||||||
|
return [self dictionaryForServerType:serverType];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
|
||||||
|
NSDictionary *urlParameterNames = [self urlParameterDictionary];
|
||||||
|
|
||||||
|
id key;
|
||||||
|
NSEnumerator *enumerator = [parameters_ keyEnumerator];
|
||||||
|
|
||||||
|
while ((key = [enumerator nextObject])) {
|
||||||
|
// The key from parameters_ corresponds to a key in
|
||||||
|
// urlParameterNames. The value in parameters_ gets stored in
|
||||||
|
// crashParameters with a key that is the value in
|
||||||
|
// urlParameterNames.
|
||||||
|
|
||||||
|
// For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
|
||||||
|
// urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
|
||||||
|
// URL parameter becomes [pname => "FOOBAR"].
|
||||||
|
NSString *breakpadParameterName = (NSString *)key;
|
||||||
|
NSString *urlParameter = [urlParameterNames
|
||||||
|
objectForKey:breakpadParameterName];
|
||||||
|
if (urlParameter) {
|
||||||
|
[crashParameters setObject:[parameters_ objectForKey:key]
|
||||||
|
forKey:urlParameter];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, add the parameters that were added by the application.
|
||||||
|
enumerator = [extraServerVars_ keyEnumerator];
|
||||||
|
|
||||||
|
while ((key = [enumerator nextObject])) {
|
||||||
|
NSString *urlParameterName = (NSString *)key;
|
||||||
|
NSString *urlParameterValue =
|
||||||
|
[extraServerVars_ objectForKey:urlParameterName];
|
||||||
|
[crashParameters setObject:urlParameterValue
|
||||||
|
forKey:urlParameterName];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)addServerParameter:(id)value forKey:(NSString *)key {
|
||||||
|
[extraServerVars_ setObject:value forKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (void)report {
|
||||||
|
NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
|
||||||
|
HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
|
||||||
|
NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
|
if (![self populateServerDictionary:uploadParameters]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[upload setParameters:uploadParameters];
|
||||||
|
|
||||||
|
// Add minidump file
|
||||||
|
if (minidumpContents_) {
|
||||||
|
[upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
|
||||||
|
|
||||||
|
// Send it
|
||||||
|
NSError *error = nil;
|
||||||
|
NSData *data = [upload send:&error];
|
||||||
|
NSString *result = [[NSString alloc] initWithData:data
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
const char *reportID = "ERR";
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
|
||||||
|
[[error description] UTF8String]);
|
||||||
|
} else {
|
||||||
|
NSCharacterSet *trimSet =
|
||||||
|
[NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||||
|
reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
|
||||||
|
[self logUploadWithID:reportID];
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename the minidump file according to the id returned from the server
|
||||||
|
NSString *minidumpDir =
|
||||||
|
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||||
|
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
||||||
|
|
||||||
|
NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
|
||||||
|
minidumpDir, minidumpID];
|
||||||
|
NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
|
||||||
|
minidumpDir, reportID];
|
||||||
|
|
||||||
|
const char *src = [srcString fileSystemRepresentation];
|
||||||
|
const char *dest = [destString fileSystemRepresentation];
|
||||||
|
|
||||||
|
if (rename(src, dest) == 0) {
|
||||||
|
GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \
|
||||||
|
"upload",src, dest);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// can't rename - don't worry - it's not important for users
|
||||||
|
GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n",
|
||||||
|
reportID );
|
||||||
|
}
|
||||||
|
[result release];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logFileData_) {
|
||||||
|
HTTPMultipartUpload *logUpload =
|
||||||
|
[[HTTPMultipartUpload alloc] initWithURL:url];
|
||||||
|
|
||||||
|
[uploadParameters setObject:@"log" forKey:@"type"];
|
||||||
|
[logUpload setParameters:uploadParameters];
|
||||||
|
[logUpload addFileContents:logFileData_ name:@"log"];
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
NSData *data = [logUpload send:&error];
|
||||||
|
NSString *result = [[NSString alloc] initWithData:data
|
||||||
|
encoding:NSUTF8StringEncoding];
|
||||||
|
[result release];
|
||||||
|
[logUpload release];
|
||||||
|
}
|
||||||
|
|
||||||
|
[upload release];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logUploadWithID:(const char *)uploadID {
|
||||||
|
NSString *minidumpDir =
|
||||||
|
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||||
|
NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
|
||||||
|
minidumpDir, kReporterLogFilename];
|
||||||
|
NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
|
||||||
|
[[NSDate date] timeIntervalSince1970], uploadID];
|
||||||
|
NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||||
|
if ([fileManager fileExistsAtPath:logFilePath]) {
|
||||||
|
NSFileHandle *logFileHandle =
|
||||||
|
[NSFileHandle fileHandleForWritingAtPath:logFilePath];
|
||||||
|
[logFileHandle seekToEndOfFile];
|
||||||
|
[logFileHandle writeData:logData];
|
||||||
|
[logFileHandle closeFile];
|
||||||
|
} else {
|
||||||
|
[fileManager createFileAtPath:logFilePath
|
||||||
|
contents:logData
|
||||||
|
attributes:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
- (void)dealloc {
|
||||||
|
[parameters_ release];
|
||||||
|
[minidumpContents_ release];
|
||||||
|
[logFileData_ release];
|
||||||
|
[googleDictionary_ release];
|
||||||
|
[socorroDictionary_ release];
|
||||||
|
[serverDictionary_ release];
|
||||||
|
[extraServerVars_ release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in a new issue