// Copyright (c) 2006, 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 "client/mac/sender/crash_report_sender.h" #import #import #import #import #import #import "client/apple/Framework/BreakpadDefines.h" #import "common/mac/GTMLogger.h" #import "common/mac/HTTPMultipartUpload.h" #define kLastSubmission @"LastSubmission" const int kUserCommentsMaxLength = 1500; const int kEmailMaxLength = 64; #define kApplePrefsSyncExcludeAllKey \ @"com.apple.PreferenceSync.ExcludeAllSyncKeys" #pragma mark - @interface NSView (ResizabilityExtentions) // Shifts the view vertically by the given amount. - (void)breakpad_shiftVertically:(CGFloat)offset; // Shifts the view horizontally by the given amount. - (void)breakpad_shiftHorizontally:(CGFloat)offset; @end @implementation NSView (ResizabilityExtentions) - (void)breakpad_shiftVertically:(CGFloat)offset { NSPoint origin = [self frame].origin; origin.y += offset; [self setFrameOrigin:origin]; } - (void)breakpad_shiftHorizontally:(CGFloat)offset { NSPoint origin = [self frame].origin; origin.x += offset; [self setFrameOrigin:origin]; } @end @interface NSWindow (ResizabilityExtentions) // Adjusts the window height by heightDelta relative to its current height, // keeping all the content at the same size. - (void)breakpad_adjustHeight:(CGFloat)heightDelta; @end @implementation NSWindow (ResizabilityExtentions) - (void)breakpad_adjustHeight:(CGFloat)heightDelta { [[self contentView] setAutoresizesSubviews:NO]; NSRect windowFrame = [self frame]; windowFrame.size.height += heightDelta; [self setFrame:windowFrame display:YES]; // For some reason the content view is resizing, but not adjusting its origin, // so correct it manually. [[self contentView] setFrameOrigin:NSMakePoint(0, 0)]; [[self contentView] setAutoresizesSubviews:YES]; } @end @interface NSTextField (ResizabilityExtentions) // Grows or shrinks the height of the field to the minimum required to show the // current text, preserving the existing width and origin. // Returns the change in height. - (CGFloat)breakpad_adjustHeightToFit; // Grows or shrinks the width of the field to the minimum required to show the // current text, preserving the existing height and origin. // Returns the change in width. - (CGFloat)breakpad_adjustWidthToFit; @end @implementation NSTextField (ResizabilityExtentions) - (CGFloat)breakpad_adjustHeightToFit { NSRect oldFrame = [self frame]; // Starting with the 10.5 SDK, height won't grow, so make it huge to start. NSRect presizeFrame = oldFrame; presizeFrame.size.height = MAXFLOAT; // sizeToFit will blow out the width rather than making the field taller, so // we do it manually. NSSize newSize = [[self cell] cellSizeForBounds:presizeFrame]; NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y, NSWidth(oldFrame), newSize.height); [self setFrame:newFrame]; return newSize.height - NSHeight(oldFrame); } - (CGFloat)breakpad_adjustWidthToFit { NSRect oldFrame = [self frame]; [self sizeToFit]; return NSWidth([self frame]) - NSWidth(oldFrame); } @end @interface NSButton (ResizabilityExtentions) // Resizes to fit the label using IB-style size-to-fit metrics and enforcing a // minimum width of 70, while preserving the right edge location. // Returns the change in width. - (CGFloat)breakpad_smartSizeToFit; @end @implementation NSButton (ResizabilityExtentions) - (CGFloat)breakpad_smartSizeToFit { NSRect oldFrame = [self frame]; [self sizeToFit]; NSRect newFrame = [self frame]; // sizeToFit gives much worse results that IB's Size to Fit option. This is // the amount of padding IB adds over a sizeToFit, empirically determined. const float kExtraPaddingAmount = 12; const float kMinButtonWidth = 70; // The default button size in IB. newFrame.size.width = NSWidth(newFrame) + kExtraPaddingAmount; if (NSWidth(newFrame) < kMinButtonWidth) newFrame.size.width = kMinButtonWidth; // Preserve the right edge location. newFrame.origin.x = NSMaxX(oldFrame) - NSWidth(newFrame); [self setFrame:newFrame]; return NSWidth(newFrame) - NSWidth(oldFrame); } @end #pragma mark - @interface Reporter(PrivateMethods) - (id)initWithConfigFile:(const char *)configFile; // Returns YES if it has been long enough since the last report that we should // submit a report for this crash. - (BOOL)reportIntervalElapsed; // Returns YES if we should send the report without asking the user first. - (BOOL)shouldSubmitSilently; // Returns YES if the minidump was generated on demand. - (BOOL)isOnDemand; // Returns YES if we should ask the user to provide comments. - (BOOL)shouldRequestComments; // Returns YES if we should ask the user to provide an email address. - (BOOL)shouldRequestEmail; // Shows UI to the user to ask for permission to send and any extra information // we've been instructed to request. Returns YES if the user allows the report // to be sent. - (BOOL)askUserPermissionToSend; // Returns the short description of the crash, suitable for use as a dialog // title (e.g., "The application Foo has quit unexpectedly"). - (NSString*)shortDialogMessage; // Return explanatory text about the crash and the reporter, suitable for the // body text of a dialog. - (NSString*)explanatoryDialogText; // Returns the amount of time the UI should be shown before timing out. - (NSTimeInterval)messageTimeout; // Preps the comment-prompting alert window for display: // * localizes all the elements // * resizes and adjusts layout as necessary for localization // * removes the email section if includeEmail is NO - (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail; // Rmevoes the email section of the dialog, adjusting the rest of the window // as necessary. - (void)removeEmailPrompt; // Run an alert window with the given timeout. Returns // NSRunStoppedResponse if the timeout is exceeded. A timeout of 0 // queues the message immediately in the modal run loop. - (NSInteger)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; // This method is used to periodically update the UI with how many // seconds are left in the dialog display. - (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer; // When we receive this notification, it means that the user has // begun editing the email address or comments field, and we disable // the timers so that the user has as long as they want to type // in their comments/email. - (void)controlTextDidBeginEditing:(NSNotification *)aNotification; - (void)report; @end @implementation Reporter //============================================================================= - (id)initWithConfigFile:(const char *)configFile { if ((self = [super init])) { remainingDialogTime_ = 0; uploader_ = [[Uploader alloc] initWithConfigFile:configFile]; if (!uploader_) { [self release]; return nil; } } return self; } //============================================================================= - (BOOL)askUserPermissionToSend { // Initialize Cocoa, needed to display the alert NSApplicationLoad(); // Get the timeout value for the notification. NSTimeInterval timeout = [self messageTimeout]; NSInteger buttonPressed = NSAlertAlternateReturn; // Determine whether we should create a text box for user feedback. if ([self shouldRequestComments]) { BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self]; if (!didLoadNib) { return NO; } [self configureAlertWindowIncludingEmail:[self shouldRequestEmail]]; buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout]; // Extract info from the user into the uploader_. if ([self commentsValue]) { [[uploader_ parameters] setObject:[self commentsValue] forKey:@BREAKPAD_COMMENTS]; } if ([self emailValue]) { [[uploader_ parameters] setObject:[self emailValue] forKey:@BREAKPAD_EMAIL]; } } else { // Create an alert panel to tell the user something happened NSPanel* alert = NSGetAlertPanel([self shortDialogMessage], @"%@", NSLocalizedString(@"sendReportButton", @""), NSLocalizedString(@"cancelButton", @""), nil, [self explanatoryDialogText]); // Pop the alert with an automatic timeout, and wait for the response buttonPressed = [self runModalWindow:alert withTimeout:timeout]; // Release the panel memory NSReleaseAlertPanel(alert); } return buttonPressed == NSAlertDefaultReturn; } - (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail { // Swap in localized values, making size adjustments to impacted elements as // we go. Remember that the origin is in the bottom left, so elements above // "fall" as text areas are shrunk from their overly-large IB sizes. // Localize the header. No resizing needed, as it has plenty of room. [dialogTitle_ setStringValue:[self shortDialogMessage]]; // Localize the explanatory text field. [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", [self explanatoryDialogText], NSLocalizedString(@"commentsMsg", @"")]]; CGFloat commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; [headerBox_ breakpad_shiftVertically:commentHeightDelta]; [alertWindow_ breakpad_adjustHeight:commentHeightDelta]; // Either localize the email explanation field or remove the whole email // section depending on whether or not we are asking for email. if (includeEmail) { [emailMessage_ setStringValue:NSLocalizedString(@"emailMsg", @"")]; CGFloat emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit]; [preEmailBox_ breakpad_shiftVertically:emailHeightDelta]; [alertWindow_ breakpad_adjustHeight:emailHeightDelta]; } else { [self removeEmailPrompt]; // Handles necessary resizing. } // Localize the email label, and shift the associated text field. [emailLabel_ setStringValue:NSLocalizedString(@"emailLabel", @"")]; CGFloat emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit]; [emailEntryField_ breakpad_shiftHorizontally:emailLabelWidthDelta]; // Localize the privacy policy label, and keep it right-aligned to the arrow. [privacyLinkLabel_ setStringValue:NSLocalizedString(@"privacyLabel", @"")]; CGFloat privacyLabelWidthDelta = [privacyLinkLabel_ breakpad_adjustWidthToFit]; [privacyLinkLabel_ breakpad_shiftHorizontally:(-privacyLabelWidthDelta)]; // Ensure that the email field and the privacy policy link don't overlap. CGFloat kMinControlPadding = 8; CGFloat maxEmailFieldWidth = NSMinX([privacyLinkLabel_ frame]) - NSMinX([emailEntryField_ frame]) - kMinControlPadding; if (NSWidth([emailEntryField_ bounds]) > maxEmailFieldWidth && maxEmailFieldWidth > 0) { NSSize emailSize = [emailEntryField_ frame].size; emailSize.width = maxEmailFieldWidth; [emailEntryField_ setFrameSize:emailSize]; } // Localize the placeholder text. [[commentsEntryField_ cell] setPlaceholderString:NSLocalizedString(@"commentsPlaceholder", @"")]; [[emailEntryField_ cell] setPlaceholderString:NSLocalizedString(@"emailPlaceholder", @"")]; // Localize the buttons, and keep the cancel button at the right distance. [sendButton_ setTitle:NSLocalizedString(@"sendReportButton", @"")]; CGFloat sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit]; [cancelButton_ breakpad_shiftHorizontally:(-sendButtonWidthDelta)]; [cancelButton_ setTitle:NSLocalizedString(@"cancelButton", @"")]; [cancelButton_ breakpad_smartSizeToFit]; } - (void)removeEmailPrompt { [emailSectionBox_ setHidden:YES]; CGFloat emailSectionHeight = NSHeight([emailSectionBox_ frame]); [preEmailBox_ breakpad_shiftVertically:(-emailSectionHeight)]; [alertWindow_ breakpad_adjustHeight:(-emailSectionHeight)]; } - (NSInteger)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { // Queue a |stopModal| message to be performed in |timeout| seconds. if (timeout > 0.001) { remainingDialogTime_ = timeout; SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:); messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:updateSelector userInfo:nil repeats:YES]; } // Run the window modally and wait for either a |stopModal| message or a // button click. [NSApp activateIgnoringOtherApps:YES]; NSInteger returnMethod = [NSApp runModalForWindow:window]; return returnMethod; } - (IBAction)sendReport:(id)sender { // Force the text fields to end editing so text for the currently focused // field will be commited. [alertWindow_ makeFirstResponder:alertWindow_]; [alertWindow_ orderOut:self]; // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| // matches the AppKit function NSRunAlertPanel() [NSApp stopModalWithCode:NSAlertDefaultReturn]; } // UI Button Actions //============================================================================= - (IBAction)cancel:(id)sender { [alertWindow_ orderOut:self]; // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| // matches the AppKit function NSRunAlertPanel() [NSApp stopModalWithCode:NSAlertAlternateReturn]; } - (IBAction)showPrivacyPolicy:(id)sender { // Get the localized privacy policy URL and open it in the default browser. NSURL* privacyPolicyURL = [NSURL URLWithString:NSLocalizedString(@"privacyPolicyURL", @"")]; [[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL]; } // Text Field Delegate Methods //============================================================================= - (BOOL) control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector { BOOL result = NO; // If the user has entered text on the comment field, don't end // editing on "return". if (control == commentsEntryField_ && commandSelector == @selector(insertNewline:) && [[textView string] length] > 0) { [textView insertNewlineIgnoringFieldEditor:self]; result = YES; } return result; } - (void)controlTextDidBeginEditing:(NSNotification *)aNotification { [messageTimer_ invalidate]; [self setCountdownMessage:@""]; } - (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer { remainingDialogTime_ -= 1; NSString *countdownMessage; NSString *formatString; int displayedTimeLeft; // This can be either minutes or seconds. if (remainingDialogTime_ > 59) { // calculate minutes remaining for UI purposes displayedTimeLeft = (int)(remainingDialogTime_ / 60); if (displayedTimeLeft == 1) { formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @""); } else { formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @""); } } else { displayedTimeLeft = (int)remainingDialogTime_; if (displayedTimeLeft == 1) { formatString = NSLocalizedString(@"countdownMsgSecondSingular", @""); } else { formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @""); } } countdownMessage = [NSString stringWithFormat:formatString, displayedTimeLeft]; if (remainingDialogTime_ <= 30) { [countdownLabel_ setTextColor:[NSColor redColor]]; } [self setCountdownMessage:countdownMessage]; if (remainingDialogTime_ <= 0) { [messageTimer_ invalidate]; [NSApp stopModal]; } } #pragma mark Accessors #pragma mark - //============================================================================= - (NSString *)commentsValue { return [[commentsValue_ retain] autorelease]; } - (void)setCommentsValue:(NSString *)value { if (commentsValue_ != value) { [commentsValue_ release]; commentsValue_ = [value copy]; } } - (NSString *)emailValue { return [[emailValue_ retain] autorelease]; } - (void)setEmailValue:(NSString *)value { if (emailValue_ != value) { [emailValue_ release]; emailValue_ = [value copy]; } } - (NSString *)countdownMessage { return [[countdownMessage_ retain] autorelease]; } - (void)setCountdownMessage:(NSString *)value { if (countdownMessage_ != value) { [countdownMessage_ release]; countdownMessage_ = [value copy]; } } #pragma mark - //============================================================================= - (BOOL)reportIntervalElapsed { float interval = [[[uploader_ parameters] objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue]; NSString *program = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *programDict = [NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]]; NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission]; NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; NSTimeInterval now = CFAbsoluteTimeGetCurrent(); NSTimeInterval spanSeconds = (now - lastTime); [programDict setObject:[NSNumber numberWithDouble:now] forKey:kLastSubmission]; [ud setObject:programDict forKey:program]; [ud synchronize]; // If we've specified an interval and we're within that time, don't ask the // user if we should report GTMLoggerDebug(@"Reporter Interval: %f", interval); if (interval > spanSeconds) { GTMLoggerDebug(@"Within throttling interval, not sending report"); return NO; } return YES; } - (BOOL)isOnDemand { return [[[uploader_ parameters] objectForKey:@BREAKPAD_ON_DEMAND] isEqualToString:@"YES"]; } - (BOOL)shouldSubmitSilently { return [[[uploader_ parameters] objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]; } - (BOOL)shouldRequestComments { return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_COMMENTS] isEqualToString:@"YES"]; } - (BOOL)shouldRequestEmail { return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_EMAIL] isEqualToString:@"YES"]; } - (NSString*)shortDialogMessage { NSString *displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; if (![displayName length]) displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; if ([self isOnDemand]) { // Local variable to pacify clang's -Wformat-extra-args. NSString* format = NSLocalizedString(@"noCrashDialogHeader", @""); return [NSString stringWithFormat:format, displayName]; } else { // Local variable to pacify clang's -Wformat-extra-args. NSString* format = NSLocalizedString(@"crashDialogHeader", @""); return [NSString stringWithFormat:format, displayName]; } } - (NSString*)explanatoryDialogText { NSString *displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; if (![displayName length]) displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; NSString *vendor = [[uploader_ parameters] objectForKey:@BREAKPAD_VENDOR]; if (![vendor length]) vendor = @"unknown vendor"; if ([self isOnDemand]) { // Local variable to pacify clang's -Wformat-extra-args. NSString* format = NSLocalizedString(@"noCrashDialogMsg", @""); return [NSString stringWithFormat:format, vendor, displayName]; } else { // Local variable to pacify clang's -Wformat-extra-args. NSString* format = NSLocalizedString(@"crashDialogMsg", @""); return [NSString stringWithFormat:format, vendor]; } } - (NSTimeInterval)messageTimeout { // Get the timeout value for the notification. NSTimeInterval timeout = [[[uploader_ parameters] objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] floatValue]; // Require a timeout of at least a minute (except 0, which means no timeout). if (timeout > 0.001 && timeout < 60.0) { timeout = 60.0; } return timeout; } - (void)report { [uploader_ report]; } //============================================================================= - (void)dealloc { [uploader_ release]; [super dealloc]; } - (void)awakeFromNib { [emailEntryField_ setMaximumLength:kEmailMaxLength]; [commentsEntryField_ setMaximumLength:kUserCommentsMaxLength]; } @end //============================================================================= @implementation LengthLimitingTextField - (void)setMaximumLength:(NSUInteger)maxLength { maximumLength_ = maxLength; } // This is the method we're overriding in NSTextField, which lets us // limit the user's input if it makes the string too long. - (BOOL) textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString { // Sometimes the range comes in invalid, so reject if we can't // figure out if the replacement text is too long. if (affectedCharRange.location == NSNotFound) { return NO; } // Figure out what the new string length would be, taking into // account user selections. NSUInteger newStringLength = [[textView string] length] - affectedCharRange.length + [replacementString length]; if (newStringLength > maximumLength_) { return NO; } else { return YES; } } // Cut, copy, and paste have to be caught specifically since there is no menu. - (BOOL)performKeyEquivalent:(NSEvent*)event { // Only handle the key equivalent if |self| is the text field with focus. NSText* fieldEditor = [self currentEditor]; if (fieldEditor != nil) { // Check for a single "Command" modifier NSUInteger modifiers = [event modifierFlags]; modifiers &= NSDeviceIndependentModifierFlagsMask; if (modifiers == NSCommandKeyMask) { // Now, check for Select All, Cut, Copy, or Paste key equivalents. NSString* characters = [event characters]; // Select All is Command-A. if ([characters isEqualToString:@"a"]) { [fieldEditor selectAll:self]; return YES; // Cut is Command-X. } else if ([characters isEqualToString:@"x"]) { [fieldEditor cut:self]; return YES; // Copy is Command-C. } else if ([characters isEqualToString:@"c"]) { [fieldEditor copy:self]; return YES; // Paste is Command-V. } else if ([characters isEqualToString:@"v"]) { [fieldEditor paste:self]; return YES; } } } // Let the super class handle the rest (e.g. Command-Period will cancel). return [super performKeyEquivalent:event]; } @end //============================================================================= int main(int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; #if DEBUG // Log to stderr in debug builds. [GTMLogger setSharedLogger:[GTMLogger standardLoggerWithStderr]]; #endif GTMLoggerDebug(@"Reporter Launched, argc=%d", argc); // The expectation is that there will be one argument which is the path // to the configuration file if (argc != 2) { exit(1); } Reporter *reporter = [[Reporter alloc] initWithConfigFile:argv[1]]; if (!reporter) { GTMLoggerDebug(@"reporter initialization failed"); exit(1); } // only submit a report if we have not recently crashed in the past BOOL shouldSubmitReport = [reporter reportIntervalElapsed]; BOOL okayToSend = NO; // ask user if we should send if (shouldSubmitReport) { if ([reporter shouldSubmitSilently]) { GTMLoggerDebug(@"Skipping confirmation and sending report"); okayToSend = YES; } else { okayToSend = [reporter askUserPermissionToSend]; } } // If we're running as root, switch over to nobody if (getuid() == 0 || geteuid() == 0) { struct passwd *pw = getpwnam("nobody"); // If we can't get a non-root uid, don't send the report if (!pw) { GTMLoggerDebug(@"!pw - %s", strerror(errno)); exit(0); } if (setgid(pw->pw_gid) == -1) { GTMLoggerDebug(@"setgid(pw->pw_gid) == -1 - %s", strerror(errno)); exit(0); } if (setuid(pw->pw_uid) == -1) { GTMLoggerDebug(@"setuid(pw->pw_uid) == -1 - %s", strerror(errno)); exit(0); } } else { GTMLoggerDebug(@"getuid() !=0 || geteuid() != 0"); } if (okayToSend && shouldSubmitReport) { GTMLoggerDebug(@"Sending Report"); [reporter report]; GTMLoggerDebug(@"Report Sent!"); } else { GTMLoggerDebug(@"Not sending crash report okayToSend=%d, "\ "shouldSubmitReport=%d", okayToSend, shouldSubmitReport); } GTMLoggerDebug(@"Exiting with no errors"); // Cleanup [reporter release]; [pool release]; return 0; }