/ src / tools / mac / symupload / symupload.mm
symupload.mm
  1  // Copyright 2006 Google LLC
  2  //
  3  // Redistribution and use in source and binary forms, with or without
  4  // modification, are permitted provided that the following conditions are
  5  // met:
  6  //
  7  //     * Redistributions of source code must retain the above copyright
  8  // notice, this list of conditions and the following disclaimer.
  9  //     * Redistributions in binary form must reproduce the above
 10  // copyright notice, this list of conditions and the following disclaimer
 11  // in the documentation and/or other materials provided with the
 12  // distribution.
 13  //     * Neither the name of Google LLC nor the names of its
 14  // contributors may be used to endorse or promote products derived from
 15  // this software without specific prior written permission.
 16  //
 17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 21  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 22  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 23  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 24  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 25  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 27  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28  
 29  // symupload.mm: Upload a symbol file to a HTTP server.  The upload is sent as
 30  // a multipart/form-data POST request with the following parameters:
 31  //  code_file: the basename of the module, e.g. "app"
 32  //  debug_file: the basename of the debugging file, e.g. "app"
 33  //  debug_identifier: the debug file's identifier, usually consisting of
 34  //                    the guid and age embedded in the pdb, e.g.
 35  //                    "11111111BBBB3333DDDD555555555555F"
 36  //  os: the operating system that the module was built for
 37  //  cpu: the CPU that the module was built for (x86 or ppc)
 38  //  symbol_file: the contents of the breakpad-format symbol file
 39  
 40  #include <fcntl.h>
 41  #include <sys/stat.h>
 42  #include <unistd.h>
 43  
 44  #include <Foundation/Foundation.h>
 45  
 46  #include "HTTPMultipartUpload.h"
 47  #include "HTTPPutRequest.h"
 48  #include "SymbolCollectorClient.h"
 49  #include "common/mac/dump_syms.h"
 50  
 51  using google_breakpad::DumpSymbols;
 52  
 53  NSString* const kBreakpadSymbolType = @"BREAKPAD";
 54  NSString* const kMachOSymbolType = @"MACHO";
 55  NSString* const kDSYMSymbolType = @"DSYM";
 56  
 57  typedef enum { kSymUploadProtocolV1, kSymUploadProtocolV2 } SymUploadProtocol;
 58  
 59  typedef enum {
 60    kResultSuccess = 0,
 61    kResultFailure = 1,
 62    kResultAlreadyExists = 2
 63  } Result;
 64  
 65  typedef struct {
 66    NSString* symbolsPath;
 67    NSString* uploadURLStr;
 68    SymUploadProtocol symUploadProtocol;
 69    NSString* apiKey;
 70    BOOL force;
 71    Result result;
 72    NSString* type;
 73    NSString* codeFile;
 74    NSString* debugID;
 75    NSString* productName;
 76  } Options;
 77  
 78  //=============================================================================
 79  static NSArray* ModuleDataForSymbolFile(NSString* file) {
 80    NSFileHandle* fh = [NSFileHandle fileHandleForReadingAtPath:file];
 81    NSData* data = [fh readDataOfLength:1024];
 82    NSString* str = [[NSString alloc] initWithData:data
 83                                          encoding:NSUTF8StringEncoding];
 84    NSScanner* scanner = [NSScanner scannerWithString:str];
 85    NSString* line;
 86    NSMutableArray* parts = nil;
 87    const int MODULE_ID_INDEX = 3;
 88  
 89    if ([scanner scanUpToString:@"\n" intoString:&line]) {
 90      parts = [[NSMutableArray alloc] init];
 91      NSScanner* moduleInfoScanner = [NSScanner scannerWithString:line];
 92      NSString* moduleInfo;
 93      // Get everything BEFORE the module name.  None of these properties
 94      // can have spaces.
 95      for (int i = 0; i <= MODULE_ID_INDEX; i++) {
 96        [moduleInfoScanner scanUpToString:@" " intoString:&moduleInfo];
 97        [parts addObject:moduleInfo];
 98      }
 99  
100      // Now get the module name. This can have a space so we scan to
101      // the end of the line.
102      [moduleInfoScanner scanUpToString:@"\n" intoString:&moduleInfo];
103      [parts addObject:moduleInfo];
104    }
105  
106    [str release];
107  
108    return parts;
109  }
110  
111  //=============================================================================
112  static void StartSymUploadProtocolV1(Options* options,
113                                       NSString* OS,
114                                       NSString* CPU,
115                                       NSString* debugID,
116                                       NSString* debugFile) {
117    NSURL* url = [NSURL URLWithString:options->uploadURLStr];
118    HTTPMultipartUpload* ul = [[HTTPMultipartUpload alloc] initWithURL:url];
119    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
120  
121    // Add parameters
122    [parameters setObject:debugID forKey:@"debug_identifier"];
123    [parameters setObject:OS forKey:@"os"];
124    [parameters setObject:CPU forKey:@"cpu"];
125    [parameters setObject:debugFile forKey:@"debug_file"];
126    [parameters setObject:debugFile forKey:@"code_file"];
127    [ul setParameters:parameters];
128  
129    NSArray* keys = [parameters allKeys];
130    int count = [keys count];
131    for (int i = 0; i < count; ++i) {
132      NSString* key = [keys objectAtIndex:i];
133      NSString* value = [parameters objectForKey:key];
134      fprintf(stdout, "'%s' = '%s'\n", [key UTF8String], [value UTF8String]);
135    }
136  
137    // Add file
138    [ul addFileAtPath:options->symbolsPath name:@"symbol_file"];
139  
140    // Send it
141    NSError* error = nil;
142    NSData* data = [ul send:&error];
143    NSString* result = [[NSString alloc] initWithData:data
144                                             encoding:NSUTF8StringEncoding];
145    int status = [[ul response] statusCode];
146  
147    fprintf(stdout, "Send: %s\n",
148            error ? [[error description] UTF8String] : "No Error");
149    fprintf(stdout, "Response: %d\n", status);
150    fprintf(stdout, "Result: %lu bytes\n%s\n", (unsigned long)[data length],
151            [result UTF8String]);
152  
153    [result release];
154    [ul release];
155    options->result = (!error && status == 200) ? kResultSuccess : kResultFailure;
156  }
157  
158  //=============================================================================
159  static void StartSymUploadProtocolV2(Options* options,
160                                       NSString* debugID,
161                                       NSString* debugFile) {
162    options->result = kResultFailure;
163  
164    // Only check status of BREAKPAD symbols, because the v2 protocol doesn't
165    // (yet) have a way to check status of other symbol types.
166    if (!options->force && [options->type isEqualToString:kBreakpadSymbolType]) {
167      SymbolStatus symbolStatus =
168          [SymbolCollectorClient checkSymbolStatusOnServer:options->uploadURLStr
169                                                withAPIKey:options->apiKey
170                                             withDebugFile:debugFile
171                                               withDebugID:debugID];
172      if (symbolStatus == SymbolStatusFound) {
173        fprintf(stdout, "Symbol file already exists, upload aborted."
174                        " Use \"-f\" to overwrite.\n");
175        options->result = kResultAlreadyExists;
176        return;
177      } else if (symbolStatus == SymbolStatusUnknown) {
178        fprintf(stdout, "Failed to get check for existing symbol.\n");
179        return;
180      }
181    }
182  
183    UploadURLResponse* URLResponse =
184        [SymbolCollectorClient createUploadURLOnServer:options->uploadURLStr
185                                            withAPIKey:options->apiKey];
186    if (URLResponse == nil) {
187      return;
188    }
189  
190    NSURL* uploadURL = [NSURL URLWithString:[URLResponse uploadURL]];
191    HTTPPutRequest* putRequest = [[HTTPPutRequest alloc] initWithURL:uploadURL];
192    [putRequest setFile:options->symbolsPath];
193  
194    NSError* error = nil;
195    NSData* data = [putRequest send:&error];
196    NSString* result = [[NSString alloc] initWithData:data
197                                             encoding:NSUTF8StringEncoding];
198    int responseCode = [[putRequest response] statusCode];
199    [putRequest release];
200  
201    if (error || responseCode != 200) {
202      fprintf(stdout, "Failed to upload symbol file.\n");
203      fprintf(stdout, "Response code: %d\n", responseCode);
204      fprintf(stdout, "Response:\n");
205      fprintf(stdout, "%s\n", [result UTF8String]);
206      return;
207    }
208  
209    CompleteUploadResult completeUploadResult =
210        [SymbolCollectorClient completeUploadOnServer:options->uploadURLStr
211                                           withAPIKey:options->apiKey
212                                        withUploadKey:[URLResponse uploadKey]
213                                        withDebugFile:debugFile
214                                          withDebugID:debugID
215                                             withType:options->type
216                                      withProductName:options->productName];
217    [URLResponse release];
218    if (completeUploadResult == CompleteUploadResultError) {
219      fprintf(stdout, "Failed to complete upload.\n");
220      return;
221    } else if (completeUploadResult == CompleteUploadResultDuplicateData) {
222      fprintf(stdout, "Uploaded file checksum matched existing file checksum,"
223                      " no change necessary.\n");
224    } else {
225      fprintf(stdout, "Successfully sent the symbol file.\n");
226    }
227    options->result = kResultSuccess;
228  }
229  
230  //=============================================================================
231  static void Start(Options* options) {
232    // If non-BREAKPAD upload special-case.
233    if (![options->type isEqualToString:kBreakpadSymbolType]) {
234      StartSymUploadProtocolV2(options, options->debugID, options->codeFile);
235      return;
236    }
237  
238    NSArray* moduleParts = ModuleDataForSymbolFile(options->symbolsPath);
239    // MODULE <os> <cpu> <uuid> <module-name>
240    // 0      1    2     3      4
241    NSString* OS = [moduleParts objectAtIndex:1];
242    NSString* CPU = [moduleParts objectAtIndex:2];
243    NSMutableString* debugID =
244        [NSMutableString stringWithString:[moduleParts objectAtIndex:3]];
245    [debugID replaceOccurrencesOfString:@"-"
246                             withString:@""
247                                options:0
248                                  range:NSMakeRange(0, [debugID length])];
249    NSString* debugFile = [moduleParts objectAtIndex:4];
250  
251    if (options->symUploadProtocol == kSymUploadProtocolV1) {
252      StartSymUploadProtocolV1(options, OS, CPU, debugID, debugFile);
253    } else if (options->symUploadProtocol == kSymUploadProtocolV2) {
254      StartSymUploadProtocolV2(options, debugID, debugFile);
255    }
256  }
257  
258  //=============================================================================
259  static void Usage(int argc, const char* argv[]) {
260    fprintf(stderr, "Submit symbol information.\n");
261    fprintf(stderr, "Usage: %s [options] <symbol-file> <upload-URL>\n", argv[0]);
262    fprintf(stderr, "<symbol-file> should be created by using the dump_syms "
263                    "tool.\n");
264    fprintf(stderr, "<upload-URL> is the destination for the upload.\n");
265    fprintf(stderr, "Options:\n");
266    fprintf(stderr, "\t-p <protocol>: protocol to use for upload, accepts "
267                    "[\"sym-upload-v1\", \"sym-upload-v2\"]. Default is "
268                    "\"sym-upload-v1\".\n");
269    fprintf(stderr, "\t-k <api-key>: secret for authentication with upload "
270                    "server. [Only in sym-upload-v2 protocol mode]\n");
271    fprintf(stderr, "\t-f: Overwrite symbol file on server if already present. "
272                    "[Only in sym-upload-v2 protocol mode]\n");
273    fprintf(
274        stderr,
275        "\t-t: <symbol-type> Explicitly set symbol upload type ("
276        "default is 'breakpad').\n"
277        "\t One of ['breakpad', 'elf', 'pe', 'macho', 'debug_only', 'dwp', "
278        "'dsym', 'pdb'].\n"
279        "\t Note: When this flag is set to anything other than 'breakpad', then "
280        "the '-c' and '-i' flags must also be set.\n");
281    fprintf(stderr, "\t-c: <code-file> Explicitly set 'code_file' for symbol "
282                    "upload (basename of executable).\n");
283    fprintf(stderr, "\t-i: <debug-id> Explicitly set 'debug_id' for symbol "
284                    "upload (typically build ID of executable). The debug-id for "
285                    "symbol-types 'dsym' and 'macho' will be determined "
286                    "automatically. \n");
287    fprintf(stderr, "\t-n: <product-name> Optionally set 'product_name' for "
288                    "symbol upload\n");
289    fprintf(stderr, "\t-h: Usage\n");
290    fprintf(stderr, "\t-?: Usage\n");
291    fprintf(stderr, "\n");
292    fprintf(stderr, "Exit codes:\n");
293    fprintf(stderr, "\t%d: Success\n", kResultSuccess);
294    fprintf(stderr, "\t%d: Failure\n", kResultFailure);
295    fprintf(stderr,
296            "\t%d: Symbol file already exists on server (and -f was not "
297            "specified).\n",
298            kResultAlreadyExists);
299    fprintf(stderr,
300            "\t   [This exit code will only be returned by the sym-upload-v2 "
301            "protocol.\n");
302    fprintf(stderr,
303            "\t    The sym-upload-v1 protocol can return either Success or "
304            "Failure\n");
305    fprintf(stderr, "\t    in this case, and the action taken by the server is "
306                    "unspecified.]\n");
307    fprintf(stderr, "\n");
308    fprintf(stderr, "Examples:\n");
309    fprintf(stderr, "  With 'sym-upload-v1':\n");
310    fprintf(stderr, "    %s path/to/symbol_file http://myuploadserver\n",
311            argv[0]);
312    fprintf(stderr, "  With 'sym-upload-v2':\n");
313    fprintf(stderr, "    [Defaulting to symbol type 'BREAKPAD']\n");
314    fprintf(stderr,
315            "    %s -p sym-upload-v2 -k mysecret123! "
316            "path/to/symbol_file http://myuploadserver\n",
317            argv[0]);
318    fprintf(stderr, "    [Explicitly set symbol type to 'macho']\n");
319    fprintf(stderr,
320            "    %s -p sym-upload-v2 -k mysecret123! -t macho "
321            "-c app -i 11111111BBBB3333DDDD555555555555F "
322            "path/to/symbol_file http://myuploadserver\n",
323            argv[0]);
324  }
325  
326  //=============================================================================
327  static void SetupOptions(int argc, const char* argv[], Options* options) {
328    // Set default options values.
329    options->symUploadProtocol = kSymUploadProtocolV1;
330    options->apiKey = nil;
331    options->type = kBreakpadSymbolType;
332    options->codeFile = nil;
333    options->debugID = nil;
334    options->force = NO;
335    options->productName = nil;
336  
337    extern int optind;
338    int ch;
339  
340    while ((ch = getopt(argc, (char* const*)argv, "p:k:t:c:i:n:hf?")) != -1) {
341      switch (ch) {
342        case 'p':
343          if (strcmp(optarg, "sym-upload-v2") == 0) {
344            options->symUploadProtocol = kSymUploadProtocolV2;
345            break;
346          } else if (strcmp(optarg, "sym-upload-v1") == 0) {
347            // This is already the default but leave in case that changes.
348            options->symUploadProtocol = kSymUploadProtocolV1;
349            break;
350          }
351          Usage(argc, argv);
352          exit(0);
353          break;
354        case 'k':
355          options->apiKey = [NSString stringWithCString:optarg
356                                               encoding:NSASCIIStringEncoding];
357          break;
358        case 't': {
359          // This is really an enum, so treat as upper-case for consistency with
360          // enum naming convention on server-side.
361          options->type = [[NSString stringWithCString:optarg
362                                              encoding:NSASCIIStringEncoding]
363              uppercaseString];
364          break;
365        }
366        case 'c':
367          options->codeFile = [NSString stringWithCString:optarg
368                                                 encoding:NSASCIIStringEncoding];
369          break;
370        case 'i':
371          options->debugID = [NSString stringWithCString:optarg
372                                                encoding:NSASCIIStringEncoding];
373          break;
374        case 'n':
375          options->productName =
376              [NSString stringWithCString:optarg
377                                 encoding:NSASCIIStringEncoding];
378          break;
379        case 'f':
380          options->force = YES;
381          break;
382        default:
383          Usage(argc, argv);
384          exit(0);
385          break;
386      }
387    }
388  
389    if ((argc - optind) != 2) {
390      fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]);
391      Usage(argc, argv);
392      exit(1);
393    }
394  
395    int fd = open(argv[optind], O_RDONLY);
396    if (fd < 0) {
397      fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
398      exit(1);
399    }
400  
401    struct stat statbuf;
402    if (fstat(fd, &statbuf) < 0) {
403      fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
404      close(fd);
405      exit(1);
406    }
407    close(fd);
408  
409    if (!S_ISREG(statbuf.st_mode)) {
410      fprintf(stderr, "%s: %s: not a regular file\n", argv[0], argv[optind]);
411      exit(1);
412    }
413  
414    bool isBreakpadUpload = [options->type isEqualToString:kBreakpadSymbolType];
415    bool hasCodeFile = options->codeFile != nil;
416    bool hasDebugID = options->debugID != nil;
417    if (isBreakpadUpload && (hasCodeFile || hasDebugID)) {
418      fprintf(stderr, "\n");
419      fprintf(stderr,
420              "%s: -c and -i should only be specified for non-breakpad "
421              "symbol upload types.\n",
422              argv[0]);
423      fprintf(stderr, "\n");
424      Usage(argc, argv);
425      exit(1);
426    }
427  
428    if (!isBreakpadUpload && hasCodeFile && !hasDebugID &&
429        ([options->type isEqualToString:kMachOSymbolType] ||
430         [options->type isEqualToString:kDSYMSymbolType])) {
431      DumpSymbols dump_symbols(SYMBOLS_AND_FILES | INLINES, false);
432      if (dump_symbols.Read(argv[optind])) {
433        std::string identifier = dump_symbols.Identifier();
434        if (identifier.empty()) {
435          fprintf(stderr, "\n");
436          fprintf(stderr,
437                  "%s: Unable to determine debug-id. Please specify with '-i'.\n",
438                  argv[0]);
439          fprintf(stderr, "\n");
440          Usage(argc, argv);
441          exit(1);
442        }
443        options->debugID = [NSString stringWithUTF8String:identifier.c_str()];
444        hasDebugID = true;
445      }
446    }
447  
448    if (!isBreakpadUpload && (!hasCodeFile || !hasDebugID)) {
449      fprintf(stderr, "\n");
450      fprintf(stderr,
451              "%s: -c and -i must be specified for non-breakpad "
452              "symbol upload types.\n",
453              argv[0]);
454      fprintf(stderr, "\n");
455      Usage(argc, argv);
456      exit(1);
457    }
458  
459    options->symbolsPath = [NSString stringWithUTF8String:argv[optind]];
460    options->uploadURLStr = [NSString stringWithUTF8String:argv[optind + 1]];
461  }
462  
463  //=============================================================================
464  int main(int argc, const char* argv[]) {
465    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
466    Options options;
467  
468    bzero(&options, sizeof(Options));
469    SetupOptions(argc, argv, &options);
470    Start(&options);
471  
472    [pool release];
473    return options.result;
474  }