Add native symbol uploads to Mac OS symupload tool.

- sym-upload-v2 protocol only.
- Supports elf, dwp, debug_only, macho, dsym, pe, and pdb (with the classic mode being called 'breakpad').

Change-Id: I68c0065aec3a7ffe29b364dd9e2e1dbdb58e3e5d
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/2357528
Reviewed-by: Mark Mentovai <mark@chromium.org>
diff --git a/src/common/mac/SymbolCollectorClient.h b/src/common/mac/SymbolCollectorClient.h
index 1f27f23..9e955c8 100644
--- a/src/common/mac/SymbolCollectorClient.h
+++ b/src/common/mac/SymbolCollectorClient.h
@@ -95,7 +95,8 @@
                                     withAPIKey:(NSString*)APIKey
                                  withUploadKey:(NSString*)uploadKey
                                  withDebugFile:(NSString*)debugFile
-                                   withDebugID:(NSString*)debugID;
+                                   withDebugID:(NSString*)debugID
+                                      withType:(NSString*)type;
 
 @end
 
diff --git a/src/common/mac/SymbolCollectorClient.m b/src/common/mac/SymbolCollectorClient.m
index f7cd923..b135cde 100644
--- a/src/common/mac/SymbolCollectorClient.m
+++ b/src/common/mac/SymbolCollectorClient.m
@@ -77,9 +77,9 @@
   [allowedDebugFileCharacters
       formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
   [allowedDebugFileCharacters invert];
-  NSString* escapedDebugFile = [debugFile
-      stringByAddingPercentEncodingWithAllowedCharacters:
-          allowedDebugFileCharacters];
+  NSString* escapedDebugFile =
+      [debugFile stringByAddingPercentEncodingWithAllowedCharacters:
+                     allowedDebugFileCharacters];
 
   NSURL* URL = [NSURL
       URLWithString:[NSString
@@ -189,7 +189,8 @@
                                     withAPIKey:(NSString*)APIKey
                                  withUploadKey:(NSString*)uploadKey
                                  withDebugFile:(NSString*)debugFile
-                                   withDebugID:(NSString*)debugID {
+                                   withDebugID:(NSString*)debugID
+                                      withType:(NSString*)type {
   NSURL* URL = [NSURL
       URLWithString:[NSString
                         stringWithFormat:@"%@/v1/uploads/%@:complete?key=%@",
@@ -199,7 +200,8 @@
       [NSDictionary dictionaryWithObjectsAndKeys:debugFile, @"debug_file",
                                                  debugID, @"debug_id", nil];
   NSDictionary* jsonDictionary = [NSDictionary
-      dictionaryWithObjectsAndKeys:symbolIdDictionary, @"symbol_id", nil];
+      dictionaryWithObjectsAndKeys:symbolIdDictionary, @"symbol_id", type,
+                                   @"symbol_upload_type", nil];
   NSError* error;
   NSData* jsonData =
       [NSJSONSerialization dataWithJSONObject:jsonDictionary
diff --git a/src/tools/mac/symupload/symupload.m b/src/tools/mac/symupload/symupload.m
index 2c663fe..61c2450 100644
--- a/src/tools/mac/symupload/symupload.m
+++ b/src/tools/mac/symupload/symupload.m
@@ -48,6 +48,8 @@
 #include "HTTPPutRequest.h"
 #include "SymbolCollectorClient.h"
 
+NSString* const kBreakpadSymbolType = @"BREAKPAD";
+
 typedef enum { kSymUploadProtocolV1, kSymUploadProtocolV2 } SymUploadProtocol;
 
 typedef enum {
@@ -63,6 +65,9 @@
   NSString* apiKey;
   BOOL force;
   Result result;
+  NSString* type;
+  NSString* codeFile;
+  NSString* debugID;
 } Options;
 
 //=============================================================================
@@ -100,21 +105,20 @@
 
 //=============================================================================
 static void StartSymUploadProtocolV1(Options* options,
-                                     NSArray* moduleParts,
-                                     NSString* compactedID) {
+                                     NSString* OS,
+                                     NSString* CPU,
+                                     NSString* debugID,
+                                     NSString* debugFile) {
   NSURL* url = [NSURL URLWithString:options->uploadURLStr];
   HTTPMultipartUpload* ul = [[HTTPMultipartUpload alloc] initWithURL:url];
   NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
 
   // Add parameters
-  [parameters setObject:compactedID forKey:@"debug_identifier"];
-
-  // MODULE <os> <cpu> <uuid> <module-name>
-  // 0      1    2     3      4
-  [parameters setObject:[moduleParts objectAtIndex:1] forKey:@"os"];
-  [parameters setObject:[moduleParts objectAtIndex:2] forKey:@"cpu"];
-  [parameters setObject:[moduleParts objectAtIndex:4] forKey:@"debug_file"];
-  [parameters setObject:[moduleParts objectAtIndex:4] forKey:@"code_file"];
+  [parameters setObject:debugID forKey:@"debug_identifier"];
+  [parameters setObject:OS forKey:@"os"];
+  [parameters setObject:CPU forKey:@"cpu"];
+  [parameters setObject:debugFile forKey:@"debug_file"];
+  [parameters setObject:debugFile forKey:@"code_file"];
   [ul setParameters:parameters];
 
   NSArray* keys = [parameters allKeys];
@@ -148,12 +152,13 @@
 
 //=============================================================================
 static void StartSymUploadProtocolV2(Options* options,
-                                     NSArray* moduleParts,
-                                     NSString* debugID) {
+                                     NSString* debugID,
+                                     NSString* debugFile) {
   options->result = kResultFailure;
 
-  NSString* debugFile = [moduleParts objectAtIndex:4];
-  if (!options->force) {
+  // Only check status of BREAKPAD symbols, because the v2 protocol doesn't
+  // (yet) have a way to check status of other symbol types.
+  if (!options->force && [options->type isEqualToString:kBreakpadSymbolType]) {
     SymbolStatus symbolStatus =
         [SymbolCollectorClient checkSymbolStatusOnServer:options->uploadURLStr
                                               withAPIKey:options->apiKey
@@ -201,7 +206,8 @@
                                          withAPIKey:options->apiKey
                                       withUploadKey:[URLResponse uploadKey]
                                       withDebugFile:debugFile
-                                        withDebugID:debugID];
+                                        withDebugID:debugID
+                                           withType:options->type];
   [URLResponse release];
   if (completeUploadResult == CompleteUploadResultError) {
     fprintf(stdout, "Failed to complete upload.\n");
@@ -217,18 +223,29 @@
 
 //=============================================================================
 static void Start(Options* options) {
+  // If non-BREAKPAD upload special-case.
+  if (![options->type isEqualToString:kBreakpadSymbolType]) {
+    StartSymUploadProtocolV2(options, options->debugID, options->codeFile);
+    return;
+  }
+
   NSArray* moduleParts = ModuleDataForSymbolFile(options->symbolsPath);
-  NSMutableString* compactedID =
+  // MODULE <os> <cpu> <uuid> <module-name>
+  // 0      1    2     3      4
+  NSString* OS = [moduleParts objectAtIndex:1];
+  NSString* CPU = [moduleParts objectAtIndex:2];
+  NSMutableString* debugID =
       [NSMutableString stringWithString:[moduleParts objectAtIndex:3]];
-  [compactedID replaceOccurrencesOfString:@"-"
-                               withString:@""
-                                  options:0
-                                    range:NSMakeRange(0, [compactedID length])];
+  [debugID replaceOccurrencesOfString:@"-"
+                           withString:@""
+                              options:0
+                                range:NSMakeRange(0, [debugID length])];
+  NSString* debugFile = [moduleParts objectAtIndex:4];
 
   if (options->symUploadProtocol == kSymUploadProtocolV1) {
-    StartSymUploadProtocolV1(options, moduleParts, compactedID);
+    StartSymUploadProtocolV1(options, OS, CPU, debugID, debugFile);
   } else if (options->symUploadProtocol == kSymUploadProtocolV2) {
-    StartSymUploadProtocolV2(options, moduleParts, compactedID);
+    StartSymUploadProtocolV2(options, debugID, debugFile);
   }
 }
 
@@ -247,8 +264,21 @@
                   "server. [Only in sym-upload-v2 protocol mode]\n");
   fprintf(stderr, "\t-f: Overwrite symbol file on server if already present. "
                   "[Only in sym-upload-v2 protocol mode]\n");
+  fprintf(
+      stderr,
+      "-t:\t <symbol-type> Explicitly set symbol upload type ("
+      "default is 'breakpad').\n"
+      "\t One of ['breakpad', 'elf', 'pe', 'macho', 'debug_only', 'dwp', "
+      "'dsym', 'pdb'].\n"
+      "\t Note: When this flag is set to anything other than 'breakpad', then "
+      "the '-c' and '-i' flags must also be set.\n");
+  fprintf(stderr, "-c:\t <code-file> Explicitly set 'code_file' for symbol "
+                  "upload (basename of executable).\n");
+  fprintf(stderr, "-i:\t <debug-id> Explicitly set 'debug_id' for symbol "
+                  "upload (typically build ID of executable).\n");
   fprintf(stderr, "\t-h: Usage\n");
   fprintf(stderr, "\t-?: Usage\n");
+  fprintf(stderr, "\n");
   fprintf(stderr, "Exit codes:\n");
   fprintf(stderr, "\t%d: Success\n", kResultSuccess);
   fprintf(stderr, "\t%d: Failure\n", kResultFailure);
@@ -264,17 +294,39 @@
           "Failure\n");
   fprintf(stderr, "\t    in this case, and the action taken by the server is "
                   "unspecified.]\n");
+  fprintf(stderr, "\n");
+  fprintf(stderr, "Examples:\n");
+  fprintf(stderr, "  With 'sym-upload-v1':\n");
+  fprintf(stderr, "    %s path/to/symbol_file http://myuploadserver\n",
+          argv[0]);
+  fprintf(stderr, "  With 'sym-upload-v2':\n");
+  fprintf(stderr, "    [Defaulting to symbol type 'BREAKPAD']\n");
+  fprintf(stderr,
+          "    %s -p sym-upload-v2 -k mysecret123! "
+          "path/to/symbol_file http://myuploadserver\n",
+          argv[0]);
+  fprintf(stderr, "    [Explicitly set symbol type to 'macho']\n");
+  fprintf(stderr,
+          "    %s -p sym-upload-v2 -k mysecret123! -t macho "
+          "-c app -i 11111111BBBB3333DDDD555555555555F "
+          "path/to/symbol_file http://myuploadserver\n",
+          argv[0]);
 }
 
 //=============================================================================
 static void SetupOptions(int argc, const char* argv[], Options* options) {
-  // Set default value of symUploadProtocol.
+  // Set default options values.
   options->symUploadProtocol = kSymUploadProtocolV1;
+  options->apiKey = nil;
+  options->type = kBreakpadSymbolType;
+  options->codeFile = nil;
+  options->debugID = nil;
+  options->force = NO;
 
   extern int optind;
   char ch;
 
-  while ((ch = getopt(argc, (char* const*)argv, "p:k:hf?")) != -1) {
+  while ((ch = getopt(argc, (char* const*)argv, "p:k:t:c:i:hf?")) != -1) {
     switch (ch) {
       case 'p':
         if (strcmp(optarg, "sym-upload-v2") == 0) {
@@ -292,6 +344,24 @@
         options->apiKey = [NSString stringWithCString:optarg
                                              encoding:NSASCIIStringEncoding];
         break;
+      case 't': {
+        // This is really an enum, so treat as upper-case for consistency with
+        // enum naming convention on server-side.
+        options->type = [[NSString stringWithCString:optarg
+                                            encoding:NSASCIIStringEncoding]
+            uppercaseString];
+        break;
+      }
+      case 'c':
+        options->codeFile = [NSString stringWithCString:optarg
+                                               encoding:NSASCIIStringEncoding];
+        ;
+        break;
+      case 'i':
+        options->debugID = [NSString stringWithCString:optarg
+                                              encoding:NSASCIIStringEncoding];
+        ;
+        break;
       case 'f':
         options->force = YES;
         break;
@@ -327,6 +397,30 @@
     exit(1);
   }
 
+  bool isBreakpadUpload = [options->type isEqualToString:kBreakpadSymbolType];
+  bool hasCodeFile = options->codeFile != nil;
+  bool hasDebugID = options->debugID != nil;
+  if (isBreakpadUpload && (hasCodeFile || hasDebugID)) {
+    fprintf(stderr, "\n");
+    fprintf(stderr,
+            "%s: -c and -i should only be specified for non-breakpad "
+            "symbol upload types.\n",
+            argv[0]);
+    fprintf(stderr, "\n");
+    Usage(argc, argv);
+    exit(1);
+  }
+  if (!isBreakpadUpload && (!hasCodeFile || !hasDebugID)) {
+    fprintf(stderr, "\n");
+    fprintf(stderr,
+            "%s: -c and -i must be specified for non-breakpad "
+            "symbol upload types.\n",
+            argv[0]);
+    fprintf(stderr, "\n");
+    Usage(argc, argv);
+    exit(1);
+  }
+
   options->symbolsPath = [NSString stringWithUTF8String:argv[optind]];
   options->uploadURLStr = [NSString stringWithUTF8String:argv[optind + 1]];
 }