Add an API to Breakpad to upload custom file to the crash server.

On iOS, sending logs using the usual breakpad behavior is not possible, because
tar is not available. This allow to use Breakpad to send any file to the crash
server.

R=mark@chromium.org
Review URL: http://breakpad.appspot.com/327001

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@885 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
qsr@chromium.org 2011-11-16 17:04:28 +00:00
parent b2196565bd
commit 976930db64
5 changed files with 181 additions and 109 deletions

View file

@ -196,6 +196,11 @@ bool BreakpadHasCrashReportToUpload(BreakpadRef ref);
// Upload next report to the server.
void BreakpadUploadNextReport(BreakpadRef ref);
// Upload a file to the server. |data| is the content of the file to sent.
// |server_parameters| is additional server parameters to send.
void BreakpadUploadData(BreakpadRef ref, NSData *data,
NSDictionary *server_parameters);
#ifdef __cplusplus
}
#endif

View file

@ -152,6 +152,7 @@ class Breakpad {
void RemoveKeyValue(NSString *key);
NSString *NextCrashReportToUpload();
void UploadNextReport();
void UploadData(NSData *data, NSDictionary *server_parameters);
private:
Breakpad()
@ -425,6 +426,25 @@ void Breakpad::UploadNextReport() {
}
}
//=============================================================================
void Breakpad::UploadData(NSData *data, NSDictionary *server_parameters) {
NSMutableDictionary *config = [NSMutableDictionary dictionary];
SimpleStringDictionaryIterator it(*config_params_);
while (const KeyValueEntry *next = it.Next()) {
[config setValue:[NSString stringWithUTF8String:next->GetValue()]
forKey:[NSString stringWithUTF8String:next->GetKey()]];
}
Uploader *uploader =
[[[Uploader alloc] initWithConfig:config] autorelease];
for (NSString *key in server_parameters) {
[uploader addServerParameter:[server_parameters objectForKey:key]
forKey:key];
}
[uploader uploadData:data];
}
//=============================================================================
bool Breakpad::HandleMinidump(const char *dump_dir,
const char *minidump_id) {
@ -681,3 +701,18 @@ void BreakpadUploadNextReport(BreakpadRef ref) {
fprintf(stderr, "BreakpadUploadNextReport() : error\n");
}
}
//=============================================================================
void BreakpadUploadData(BreakpadRef ref, NSData *data,
NSDictionary *server_parameters) {
try {
// Not called at exception time
Breakpad *breakpad = (Breakpad *)ref;
if (breakpad) {
breakpad->UploadData(data, server_parameters);
}
} catch(...) { // don't let exceptions leave this C API
fprintf(stderr, "BreakpadUploadData() : error\n");
}
}

View file

@ -38,8 +38,8 @@
163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; };
163201D71443019E00C4DBF5 /* 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 */; };
1632058314442E9000C4DBF5 /* BreakpadDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 1632058214442E9000C4DBF5 /* BreakpadDefines.h */; settings = {ATTRIBUTES = (Public, ); }; };
16E02DB8147410F0008C604D /* uploader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16E02DB4147410D4008C604D /* uploader.mm */; };
3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; };
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 */; };
@ -552,8 +552,8 @@
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>"; };
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>"; };
1632058214442E9000C4DBF5 /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BreakpadDefines.h; path = Framework/BreakpadDefines.h; sourceTree = "<group>"; };
16E02DB4147410D4008C604D /* uploader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = uploader.mm; path = sender/uploader.mm; 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>"; };
33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@ -998,8 +998,8 @@
F92C56A60ECE04B6009BE4BA /* sender */ = {
isa = PBXGroup;
children = (
16E02DB4147410D4008C604D /* uploader.mm */,
163202431443201300C4DBF5 /* uploader.h */,
163202441443201300C4DBF5 /* uploader.m */,
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
@ -1741,7 +1741,7 @@
F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */,
F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */,
F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */,
163202451443201300C4DBF5 /* uploader.m in Sources */,
16E02DB8147410F0008C604D /* uploader.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -44,7 +44,6 @@ 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,
@ -66,8 +65,17 @@ extern NSString *const kDefaultServerType;
- (id)initWithConfigFile:(const char *)configFile;
- (id)initWithConfig:(NSDictionary *)config;
- (NSMutableDictionary *)parameters;
- (void)report;
// Upload the given data to the crash server.
- (void)uploadData:(NSData *)data name:(NSString *)name;
// 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;
@end

View file

@ -52,12 +52,99 @@ NSString *const kDefaultServerType = @"google";
#pragma mark -
@interface Uploader(PrivateMethods)
- (NSString *)readString;
- (NSData *)readData:(ssize_t)length;
namespace {
// Read one line from the configuration file.
NSString *readString(int fileId) {
NSMutableString *str = [NSMutableString stringWithCapacity:32];
char ch[2] = { 0 };
- (BOOL)readConfigurationData;
while (read(fileId, &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;
}
//=============================================================================
// Read |length| of binary data from the configuration file. This method will
// returns |nil| in case of error.
NSData *readData(int fileId, ssize_t length) {
NSMutableData *data = [NSMutableData dataWithLength:length];
char *bytes = (char *)[data bytes];
if (read(fileId, bytes, length) != length)
return nil;
return data;
}
//=============================================================================
// Read the configuration from the config file.
NSDictionary *readConfigurationData(const char *configFile) {
int fileId = open(configFile, O_RDONLY, 0600);
if (fileId == -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 (fileId == -1) {
return nil;
}
NSMutableDictionary *config = [NSMutableDictionary dictionary];
while (1) {
NSString *key = readString(fileId);
if (![key length])
break;
// Read the data. Try to convert to a UTF-8 string, or just save
// the data
NSString *lenStr = readString(fileId);
ssize_t len = [lenStr intValue];
NSData *data = readData(fileId, len);
id value = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
[config setObject:(value ? value : data) forKey:key];
[value release];
}
close(fileId);
return config;
}
} // namespace
#pragma mark -
@interface Uploader(PrivateMethods)
// Update |parameters_| as well as the server parameters using |config|.
- (void)translateConfigurationData:(NSDictionary *)config;
// Read the minidump referenced in |parameters_| and update |minidumpContents_|
// with its content.
- (BOOL)readMinidumpData;
// Read the log files referenced in |parameters_| and update |logFileData_|
// with their content.
- (BOOL)readLogFileData;
// Returns a unique client id (user-specific), creating a persistent
@ -80,41 +167,24 @@ NSString *const kDefaultServerType = @"google";
// 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 {
NSDictionary *config = readConfigurationData(configFile);
if (!config)
return nil;
return [self initWithConfig:config];
}
//=============================================================================
- (id)initWithConfig:(NSDictionary *)config {
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
@ -126,68 +196,21 @@ NSString *const kDefaultServerType = @"google";
[self createServerParameterDictionaries];
if (![self readConfigurationData]) {
GTMLoggerDebug(@"uploader readConfigurationData failed");
[self release];
return nil;
}
[self translateConfigurationData:config];
// 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 {
- (void)translateConfigurationData:(NSDictionary *)config {
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];
NSEnumerator *it = [config keyEnumerator];
while (NSString *key = [it nextObject]) {
// 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.
@ -195,29 +218,24 @@ NSString *const kDefaultServerType = @"google";
NSString *urlParameterKey =
[key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
if ([urlParameterKey length]) {
if (value) {
[self addServerParameter:value
id value = [config objectForKey:key];
if ([value isKindOfClass:[NSString class]]) {
[self addServerParameter:(NSString *)value
forKey:urlParameterKey];
} else {
[self addServerParameter:data
[self addServerParameter:(NSData *)value
forKey:urlParameterKey];
}
}
} else {
[parameters_ setObject:(value ? value : data) forKey:key];
[parameters_ setObject:[config objectForKey:key] 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
@ -528,24 +546,30 @@ NSString *const kDefaultServerType = @"google";
}
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];
[self uploadData:logFileData_ name:@"log" url:url];
}
[upload release];
}
- (void)uploadData:(NSData *)data name:(NSString *)name {
NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
if (![self populateServerDictionary:uploadParameters])
return;
HTTPMultipartUpload *upload =
[[HTTPMultipartUpload alloc] initWithURL:url];
[uploadParameters setObject:name forKey:@"type"];
[upload setParameters:uploadParameters];
[upload addFileContents:data name:name];
[upload send:nil];
[upload release];
}
- (void)logUploadWithID:(const char *)uploadID {
NSString *minidumpDir =
[parameters_ objectForKey:@kReporterMinidumpDirectoryKey];