Home | History | Annotate | Download | only in sender
      1 // Copyright (c) 2011, Google Inc.
      2 // All rights reserved.
      3 //
      4 // Redistribution and use in source and binary forms, with or without
      5 // modification, are permitted provided that the following conditions are
      6 // met:
      7 //
      8 //     * Redistributions of source code must retain the above copyright
      9 // notice, this list of conditions and the following disclaimer.
     10 //     * Redistributions in binary form must reproduce the above
     11 // copyright notice, this list of conditions and the following disclaimer
     12 // in the documentation and/or other materials provided with the
     13 // distribution.
     14 //     * Neither the name of Google Inc. nor the names of its
     15 // contributors may be used to endorse or promote products derived from
     16 // this software without specific prior written permission.
     17 //
     18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 #import <fcntl.h>
     31 #import <sys/stat.h>
     32 #include <TargetConditionals.h>
     33 #import <unistd.h>
     34 
     35 #import <SystemConfiguration/SystemConfiguration.h>
     36 
     37 #import "common/mac/HTTPMultipartUpload.h"
     38 
     39 #import "client/apple/Framework/BreakpadDefines.h"
     40 #import "client/mac/sender/uploader.h"
     41 #import "common/mac/GTMLogger.h"
     42 
     43 const int kMinidumpFileLengthLimit = 2 * 1024 * 1024;  // 2MB
     44 
     45 #define kApplePrefsSyncExcludeAllKey \
     46   @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
     47 
     48 NSString *const kGoogleServerType = @"google";
     49 NSString *const kSocorroServerType = @"socorro";
     50 NSString *const kDefaultServerType = @"google";
     51 
     52 #pragma mark -
     53 
     54 namespace {
     55 // Read one line from the configuration file.
     56 NSString *readString(int fileId) {
     57   NSMutableString *str = [NSMutableString stringWithCapacity:32];
     58   char ch[2] = { 0 };
     59 
     60   while (read(fileId, &ch[0], 1) == 1) {
     61     if (ch[0] == '\n') {
     62       // Break if this is the first newline after reading some other string
     63       // data.
     64       if ([str length])
     65         break;
     66     } else {
     67       [str appendString:[NSString stringWithUTF8String:ch]];
     68     }
     69   }
     70 
     71   return str;
     72 }
     73 
     74 //=============================================================================
     75 // Read |length| of binary data from the configuration file. This method will
     76 // returns |nil| in case of error.
     77 NSData *readData(int fileId, ssize_t length) {
     78   NSMutableData *data = [NSMutableData dataWithLength:length];
     79   char *bytes = (char *)[data bytes];
     80 
     81   if (read(fileId, bytes, length) != length)
     82     return nil;
     83 
     84   return data;
     85 }
     86 
     87 //=============================================================================
     88 // Read the configuration from the config file.
     89 NSDictionary *readConfigurationData(const char *configFile) {
     90   int fileId = open(configFile, O_RDONLY, 0600);
     91   if (fileId == -1) {
     92     GTMLoggerDebug(@"Couldn't open config file %s - %s",
     93                    configFile,
     94                    strerror(errno));
     95   }
     96 
     97   // we want to avoid a build-up of old config files even if they
     98   // have been incorrectly written by the framework
     99   if (unlink(configFile)) {
    100     GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
    101                    configFile,
    102                    strerror(errno));
    103   }
    104 
    105   if (fileId == -1) {
    106     return nil;
    107   }
    108 
    109   NSMutableDictionary *config = [NSMutableDictionary dictionary];
    110 
    111   while (1) {
    112     NSString *key = readString(fileId);
    113 
    114     if (![key length])
    115       break;
    116 
    117     // Read the data.  Try to convert to a UTF-8 string, or just save
    118     // the data
    119     NSString *lenStr = readString(fileId);
    120     ssize_t len = [lenStr intValue];
    121     NSData *data = readData(fileId, len);
    122     id value = [[NSString alloc] initWithData:data
    123                                      encoding:NSUTF8StringEncoding];
    124 
    125     [config setObject:(value ? value : data) forKey:key];
    126     [value release];
    127   }
    128 
    129   close(fileId);
    130   return config;
    131 }
    132 }  // namespace
    133 
    134 #pragma mark -
    135 
    136 @interface Uploader(PrivateMethods)
    137 
    138 // Update |parameters_| as well as the server parameters using |config|.
    139 - (void)translateConfigurationData:(NSDictionary *)config;
    140 
    141 // Read the minidump referenced in |parameters_| and update |minidumpContents_|
    142 // with its content.
    143 - (BOOL)readMinidumpData;
    144 
    145 // Read the log files referenced in |parameters_| and update |logFileData_|
    146 // with their content.
    147 - (BOOL)readLogFileData;
    148 
    149 // Returns a unique client id (user-specific), creating a persistent
    150 // one in the user defaults, if necessary.
    151 - (NSString*)clientID;
    152 
    153 // Returns a dictionary that can be used to map Breakpad parameter names to
    154 // URL parameter names.
    155 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
    156 
    157 // Helper method to set HTTP parameters based on server type.  This is
    158 // called right before the upload - crashParameters will contain, on exit,
    159 // URL parameters that should be sent with the minidump.
    160 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
    161 
    162 // Initialization helper to create dictionaries mapping Breakpad
    163 // parameters to URL parameters
    164 - (void)createServerParameterDictionaries;
    165 
    166 // Accessor method for the URL parameter dictionary
    167 - (NSMutableDictionary *)urlParameterDictionary;
    168 
    169 // Records the uploaded crash ID to the log file.
    170 - (void)logUploadWithID:(const char *)uploadID;
    171 @end
    172 
    173 @implementation Uploader
    174 
    175 //=============================================================================
    176 - (id)initWithConfigFile:(const char *)configFile {
    177   NSDictionary *config = readConfigurationData(configFile);
    178   if (!config)
    179     return nil;
    180 
    181   return [self initWithConfig:config];
    182 }
    183 
    184 //=============================================================================
    185 - (id)initWithConfig:(NSDictionary *)config {
    186   if ((self = [super init])) {
    187     // Because the reporter is embedded in the framework (and many copies
    188     // of the framework may exist) its not completely certain that the OS
    189     // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
    190     // Info.plist. To make sure, also set the key directly if needed.
    191     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    192     if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
    193       [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
    194     }
    195 
    196     [self createServerParameterDictionaries];
    197 
    198     [self translateConfigurationData:config];
    199 
    200     // Read the minidump into memory.
    201     [self readMinidumpData];
    202     [self readLogFileData];
    203   }
    204   return self;
    205 }
    206 
    207 //=============================================================================
    208 + (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile {
    209   return readConfigurationData([configFile fileSystemRepresentation]);
    210 }
    211 
    212 //=============================================================================
    213 - (void)translateConfigurationData:(NSDictionary *)config {
    214   parameters_ = [[NSMutableDictionary alloc] init];
    215 
    216   NSEnumerator *it = [config keyEnumerator];
    217   while (NSString *key = [it nextObject]) {
    218     // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
    219     // that indicates that it should be uploaded to the server along
    220     // with the minidump, so we treat it specially.
    221     if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
    222       NSString *urlParameterKey =
    223         [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
    224       if ([urlParameterKey length]) {
    225         id value = [config objectForKey:key];
    226         if ([value isKindOfClass:[NSString class]]) {
    227           [self addServerParameter:(NSString *)value
    228                             forKey:urlParameterKey];
    229         } else {
    230           [self addServerParameter:(NSData *)value
    231                             forKey:urlParameterKey];
    232         }
    233       }
    234     } else {
    235       [parameters_ setObject:[config objectForKey:key] forKey:key];
    236     }
    237   }
    238 
    239   // generate a unique client ID based on this host's MAC address
    240   // then add a key/value pair for it
    241   NSString *clientID = [self clientID];
    242   [parameters_ setObject:clientID forKey:@"guid"];
    243 }
    244 
    245 // Per user per machine
    246 - (NSString *)clientID {
    247   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    248   NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
    249   if (crashClientID) {
    250     return crashClientID;
    251   }
    252 
    253   // Otherwise, if we have no client id, generate one!
    254   srandom((int)[[NSDate date] timeIntervalSince1970]);
    255   long clientId1 = random();
    256   long clientId2 = random();
    257   long clientId3 = random();
    258   crashClientID = [NSString stringWithFormat:@"%lx%lx%lx",
    259                             clientId1, clientId2, clientId3];
    260 
    261   [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
    262   [ud synchronize];
    263   return crashClientID;
    264 }
    265 
    266 //=============================================================================
    267 - (BOOL)readLogFileData {
    268 #if TARGET_OS_IPHONE
    269   return NO;
    270 #else
    271   unsigned int logFileCounter = 0;
    272 
    273   NSString *logPath;
    274   size_t logFileTailSize =
    275       [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
    276 
    277   NSMutableArray *logFilenames; // An array of NSString, one per log file
    278   logFilenames = [[NSMutableArray alloc] init];
    279 
    280   char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
    281   char *tmpDir = mkdtemp(tmpDirTemplate);
    282 
    283   // Construct key names for the keys we expect to contain log file paths
    284   for(logFileCounter = 0;; logFileCounter++) {
    285     NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
    286                                      @BREAKPAD_LOGFILE_KEY_PREFIX,
    287                                      logFileCounter];
    288 
    289     logPath = [parameters_ objectForKey:logFileKey];
    290 
    291     // They should all be consecutive, so if we don't find one, assume
    292     // we're done
    293 
    294     if (!logPath) {
    295       break;
    296     }
    297 
    298     NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
    299 
    300     if (entireLogFile == nil) {
    301       continue;
    302     }
    303 
    304     NSRange fileRange;
    305 
    306     // Truncate the log file, only if necessary
    307 
    308     if ([entireLogFile length] <= logFileTailSize) {
    309       fileRange = NSMakeRange(0, [entireLogFile length]);
    310     } else {
    311       fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
    312                               logFileTailSize);
    313     }
    314 
    315     char tmpFilenameTemplate[100];
    316 
    317     // Generate a template based on the log filename
    318     sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
    319             [[logPath lastPathComponent] fileSystemRepresentation]);
    320 
    321     char *tmpFile = mktemp(tmpFilenameTemplate);
    322 
    323     NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
    324     NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
    325     [logSubdata writeToFile:tmpFileString atomically:NO];
    326 
    327     [logFilenames addObject:[tmpFileString lastPathComponent]];
    328     [entireLogFile release];
    329   }
    330 
    331   if ([logFilenames count] == 0) {
    332     [logFilenames release];
    333     logFileData_ =  nil;
    334     return NO;
    335   }
    336 
    337   // now, bzip all files into one
    338   NSTask *tarTask = [[NSTask alloc] init];
    339 
    340   [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
    341   [tarTask setLaunchPath:@"/usr/bin/tar"];
    342 
    343   NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
    344                                              @"log.tar.bz2",nil];
    345   [bzipArgs addObjectsFromArray:logFilenames];
    346 
    347   [logFilenames release];
    348 
    349   [tarTask setArguments:bzipArgs];
    350   [tarTask launch];
    351   [tarTask waitUntilExit];
    352   [tarTask release];
    353 
    354   NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
    355   logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
    356   if (logFileData_ == nil) {
    357     GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
    358     return NO;
    359   }
    360   return YES;
    361 #endif  // TARGET_OS_IPHONE
    362 }
    363 
    364 //=============================================================================
    365 - (BOOL)readMinidumpData {
    366   NSString *minidumpDir =
    367       [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
    368   NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
    369 
    370   if (![minidumpID length])
    371     return NO;
    372 
    373   NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
    374   path = [path stringByAppendingPathExtension:@"dmp"];
    375 
    376   // check the size of the minidump and limit it to a reasonable size
    377   // before attempting to load into memory and upload
    378   const char *fileName = [path fileSystemRepresentation];
    379   struct stat fileStatus;
    380 
    381   BOOL success = YES;
    382 
    383   if (!stat(fileName, &fileStatus)) {
    384     if (fileStatus.st_size > kMinidumpFileLengthLimit) {
    385       fprintf(stderr, "Breakpad Uploader: minidump file too large " \
    386               "to upload : %d\n", (int)fileStatus.st_size);
    387       success = NO;
    388     }
    389   } else {
    390       fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
    391               "file length\n");
    392       success = NO;
    393   }
    394 
    395   if (success) {
    396     minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
    397     success = ([minidumpContents_ length] ? YES : NO);
    398   }
    399 
    400   if (!success) {
    401     // something wrong with the minidump file -- delete it
    402     unlink(fileName);
    403   }
    404 
    405   return success;
    406 }
    407 
    408 #pragma mark -
    409 //=============================================================================
    410 
    411 - (void)createServerParameterDictionaries {
    412   serverDictionary_ = [[NSMutableDictionary alloc] init];
    413   socorroDictionary_ = [[NSMutableDictionary alloc] init];
    414   googleDictionary_ = [[NSMutableDictionary alloc] init];
    415   extraServerVars_ = [[NSMutableDictionary alloc] init];
    416 
    417   [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
    418   [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
    419 
    420   [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
    421   [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
    422   [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
    423   [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
    424   [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
    425   [googleDictionary_ setObject:@"guid" forKey:@"guid"];
    426 
    427   [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
    428   [socorroDictionary_ setObject:@"CrashTime"
    429                          forKey:@BREAKPAD_PROCESS_CRASH_TIME];
    430   [socorroDictionary_ setObject:@"StartupTime"
    431                          forKey:@BREAKPAD_PROCESS_START_TIME];
    432   [socorroDictionary_ setObject:@"Version"
    433                          forKey:@BREAKPAD_VERSION];
    434   [socorroDictionary_ setObject:@"ProductName"
    435                          forKey:@BREAKPAD_PRODUCT];
    436   [socorroDictionary_ setObject:@"Email"
    437                          forKey:@BREAKPAD_EMAIL];
    438 }
    439 
    440 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
    441   if (serverType == nil || [serverType length] == 0) {
    442     return [serverDictionary_ objectForKey:kDefaultServerType];
    443   }
    444   return [serverDictionary_ objectForKey:serverType];
    445 }
    446 
    447 - (NSMutableDictionary *)urlParameterDictionary {
    448   NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
    449   return [self dictionaryForServerType:serverType];
    450 
    451 }
    452 
    453 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
    454   NSDictionary *urlParameterNames = [self urlParameterDictionary];
    455 
    456   id key;
    457   NSEnumerator *enumerator = [parameters_ keyEnumerator];
    458 
    459   while ((key = [enumerator nextObject])) {
    460     // The key from parameters_ corresponds to a key in
    461     // urlParameterNames.  The value in parameters_ gets stored in
    462     // crashParameters with a key that is the value in
    463     // urlParameterNames.
    464 
    465     // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
    466     // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
    467     // URL parameter becomes [pname => "FOOBAR"].
    468     NSString *breakpadParameterName = (NSString *)key;
    469     NSString *urlParameter = [urlParameterNames
    470                                    objectForKey:breakpadParameterName];
    471     if (urlParameter) {
    472       [crashParameters setObject:[parameters_ objectForKey:key]
    473                           forKey:urlParameter];
    474     }
    475   }
    476 
    477   // Now, add the parameters that were added by the application.
    478   enumerator = [extraServerVars_ keyEnumerator];
    479 
    480   while ((key = [enumerator nextObject])) {
    481     NSString *urlParameterName = (NSString *)key;
    482     NSString *urlParameterValue =
    483       [extraServerVars_ objectForKey:urlParameterName];
    484     [crashParameters setObject:urlParameterValue
    485                         forKey:urlParameterName];
    486   }
    487   return YES;
    488 }
    489 
    490 - (void)addServerParameter:(id)value forKey:(NSString *)key {
    491   [extraServerVars_ setObject:value forKey:key];
    492 }
    493 
    494 //=============================================================================
    495 - (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error {
    496   NSString *result = [[NSString alloc] initWithData:data
    497                                            encoding:NSUTF8StringEncoding];
    498   const char *reportID = "ERR";
    499   if (error) {
    500     fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
    501             [[error description] UTF8String]);
    502   } else {
    503     NSCharacterSet *trimSet =
    504         [NSCharacterSet whitespaceAndNewlineCharacterSet];
    505     reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
    506     [self logUploadWithID:reportID];
    507   }
    508 
    509   // rename the minidump file according to the id returned from the server
    510   NSString *minidumpDir =
    511       [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
    512   NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
    513 
    514   NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
    515                                   minidumpDir, minidumpID];
    516   NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
    517                                    minidumpDir, reportID];
    518 
    519   const char *src = [srcString fileSystemRepresentation];
    520   const char *dest = [destString fileSystemRepresentation];
    521 
    522   if (rename(src, dest) == 0) {
    523     GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \
    524                   "upload",src, dest);
    525   }
    526   else {
    527     // can't rename - don't worry - it's not important for users
    528     GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n",
    529                    reportID );
    530   }
    531   [result release];
    532 }
    533 
    534 //=============================================================================
    535 - (void)report {
    536   NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
    537   HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
    538   NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
    539 
    540   if (![self populateServerDictionary:uploadParameters]) {
    541     [upload release];
    542     return;
    543   }
    544 
    545   [upload setParameters:uploadParameters];
    546 
    547   // Add minidump file
    548   if (minidumpContents_) {
    549     [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
    550 
    551     // If there is a log file, upload it together with the minidump.
    552     if (logFileData_) {
    553       [upload addFileContents:logFileData_ name:@"log"];
    554     }
    555 
    556     // Send it
    557     NSError *error = nil;
    558     NSData *data = [upload send:&error];
    559 
    560     if (![url isFileURL]) {
    561       [self handleNetworkResponse:data withError:error];
    562     } else {
    563       if (error) {
    564         fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n",
    565                 [[error description] UTF8String]);
    566       }
    567     }
    568 
    569   } else {
    570     // Minidump is missing -- upload just the log file.
    571     if (logFileData_) {
    572       [self uploadData:logFileData_ name:@"log"];
    573     }
    574   }
    575   [upload release];
    576 }
    577 
    578 - (void)uploadData:(NSData *)data name:(NSString *)name {
    579   NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
    580   NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
    581 
    582   if (![self populateServerDictionary:uploadParameters])
    583     return;
    584 
    585   HTTPMultipartUpload *upload =
    586       [[HTTPMultipartUpload alloc] initWithURL:url];
    587 
    588   [uploadParameters setObject:name forKey:@"type"];
    589   [upload setParameters:uploadParameters];
    590   [upload addFileContents:data name:name];
    591 
    592   [upload send:nil];
    593   [upload release];
    594 }
    595 
    596 - (void)logUploadWithID:(const char *)uploadID {
    597   NSString *minidumpDir =
    598       [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
    599   NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
    600       minidumpDir, kReporterLogFilename];
    601   NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
    602       [[NSDate date] timeIntervalSince1970], uploadID];
    603   NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
    604 
    605   NSFileManager *fileManager = [NSFileManager defaultManager];
    606   if ([fileManager fileExistsAtPath:logFilePath]) {
    607     NSFileHandle *logFileHandle =
    608        [NSFileHandle fileHandleForWritingAtPath:logFilePath];
    609     [logFileHandle seekToEndOfFile];
    610     [logFileHandle writeData:logData];
    611     [logFileHandle closeFile];
    612   } else {
    613     [fileManager createFileAtPath:logFilePath
    614                          contents:logData
    615                        attributes:nil];
    616   }
    617 }
    618 
    619 //=============================================================================
    620 - (NSMutableDictionary *)parameters {
    621   return parameters_;
    622 }
    623 
    624 //=============================================================================
    625 - (void)dealloc {
    626   [parameters_ release];
    627   [minidumpContents_ release];
    628   [logFileData_ release];
    629   [googleDictionary_ release];
    630   [socorroDictionary_ release];
    631   [serverDictionary_ release];
    632   [extraServerVars_ release];
    633   [super dealloc];
    634 }
    635 
    636 @end
    637