1// Copyright (c) 2006, 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// symupload.m: Upload a symbol file to a HTTP server.  The upload is sent as
31// a multipart/form-data POST request with the following parameters:
32//  code_file: the basename of the module, e.g. "app"
33//  debug_file: the basename of the debugging file, e.g. "app"
34//  debug_identifier: the debug file's identifier, usually consisting of
35//                    the guid and age embedded in the pdb, e.g.
36//                    "11111111BBBB3333DDDD555555555555F"
37//  os: the operating system that the module was built for
38//  cpu: the CPU that the module was built for (x86 or ppc)
39//  symbol_file: the contents of the breakpad-format symbol file
40
41#include <fcntl.h>
42#include <sys/stat.h>
43#include <unistd.h>
44
45#include <Foundation/Foundation.h>
46
47#include "HTTPMultipartUpload.h"
48#include "HTTPPutRequest.h"
49#include "SymbolCollectorClient.h"
50
51NSString* const kBreakpadSymbolType = @"BREAKPAD";
52
53typedef enum { kSymUploadProtocolV1, kSymUploadProtocolV2 } SymUploadProtocol;
54
55typedef enum {
56  kResultSuccess = 0,
57  kResultFailure = 1,
58  kResultAlreadyExists = 2
59} Result;
60
61typedef struct {
62  NSString* symbolsPath;
63  NSString* uploadURLStr;
64  SymUploadProtocol symUploadProtocol;
65  NSString* apiKey;
66  BOOL force;
67  Result result;
68  NSString* type;
69  NSString* codeFile;
70  NSString* debugID;
71} Options;
72
73//=============================================================================
74static NSArray* ModuleDataForSymbolFile(NSString* file) {
75  NSFileHandle* fh = [NSFileHandle fileHandleForReadingAtPath:file];
76  NSData* data = [fh readDataOfLength:1024];
77  NSString* str = [[NSString alloc] initWithData:data
78                                        encoding:NSUTF8StringEncoding];
79  NSScanner* scanner = [NSScanner scannerWithString:str];
80  NSString* line;
81  NSMutableArray* parts = nil;
82  const int MODULE_ID_INDEX = 3;
83
84  if ([scanner scanUpToString:@"\n" intoString:&line]) {
85    parts = [[NSMutableArray alloc] init];
86    NSScanner* moduleInfoScanner = [NSScanner scannerWithString:line];
87    NSString* moduleInfo;
88    // Get everything BEFORE the module name.  None of these properties
89    // can have spaces.
90    for (int i = 0; i <= MODULE_ID_INDEX; i++) {
91      [moduleInfoScanner scanUpToString:@" " intoString:&moduleInfo];
92      [parts addObject:moduleInfo];
93    }
94
95    // Now get the module name. This can have a space so we scan to
96    // the end of the line.
97    [moduleInfoScanner scanUpToString:@"\n" intoString:&moduleInfo];
98    [parts addObject:moduleInfo];
99  }
100
101  [str release];
102
103  return parts;
104}
105
106//=============================================================================
107static void StartSymUploadProtocolV1(Options* options,
108                                     NSString* OS,
109                                     NSString* CPU,
110                                     NSString* debugID,
111                                     NSString* debugFile) {
112  NSURL* url = [NSURL URLWithString:options->uploadURLStr];
113  HTTPMultipartUpload* ul = [[HTTPMultipartUpload alloc] initWithURL:url];
114  NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
115
116  // Add parameters
117  [parameters setObject:debugID forKey:@"debug_identifier"];
118  [parameters setObject:OS forKey:@"os"];
119  [parameters setObject:CPU forKey:@"cpu"];
120  [parameters setObject:debugFile forKey:@"debug_file"];
121  [parameters setObject:debugFile forKey:@"code_file"];
122  [ul setParameters:parameters];
123
124  NSArray* keys = [parameters allKeys];
125  int count = [keys count];
126  for (int i = 0; i < count; ++i) {
127    NSString* key = [keys objectAtIndex:i];
128    NSString* value = [parameters objectForKey:key];
129    fprintf(stdout, "'%s' = '%s'\n", [key UTF8String], [value UTF8String]);
130  }
131
132  // Add file
133  [ul addFileAtPath:options->symbolsPath name:@"symbol_file"];
134
135  // Send it
136  NSError* error = nil;
137  NSData* data = [ul send:&error];
138  NSString* result = [[NSString alloc] initWithData:data
139                                           encoding:NSUTF8StringEncoding];
140  int status = [[ul response] statusCode];
141
142  fprintf(stdout, "Send: %s\n",
143          error ? [[error description] UTF8String] : "No Error");
144  fprintf(stdout, "Response: %d\n", status);
145  fprintf(stdout, "Result: %lu bytes\n%s\n", (unsigned long)[data length],
146          [result UTF8String]);
147
148  [result release];
149  [ul release];
150  options->result = (!error && status == 200) ? kResultSuccess : kResultFailure;
151}
152
153//=============================================================================
154static void StartSymUploadProtocolV2(Options* options,
155                                     NSString* debugID,
156                                     NSString* debugFile) {
157  options->result = kResultFailure;
158
159  // Only check status of BREAKPAD symbols, because the v2 protocol doesn't
160  // (yet) have a way to check status of other symbol types.
161  if (!options->force && [options->type isEqualToString:kBreakpadSymbolType]) {
162    SymbolStatus symbolStatus =
163        [SymbolCollectorClient checkSymbolStatusOnServer:options->uploadURLStr
164                                              withAPIKey:options->apiKey
165                                           withDebugFile:debugFile
166                                             withDebugID:debugID];
167    if (symbolStatus == SymbolStatusFound) {
168      fprintf(stdout, "Symbol file already exists, upload aborted."
169                      " Use \"-f\" to overwrite.\n");
170      options->result = kResultAlreadyExists;
171      return;
172    } else if (symbolStatus == SymbolStatusUnknown) {
173      fprintf(stdout, "Failed to get check for existing symbol.\n");
174      return;
175    }
176  }
177
178  UploadURLResponse* URLResponse =
179      [SymbolCollectorClient createUploadURLOnServer:options->uploadURLStr
180                                          withAPIKey:options->apiKey];
181  if (URLResponse == nil) {
182    return;
183  }
184
185  NSURL* uploadURL = [NSURL URLWithString:[URLResponse uploadURL]];
186  HTTPPutRequest* putRequest = [[HTTPPutRequest alloc] initWithURL:uploadURL];
187  [putRequest setFile:options->symbolsPath];
188
189  NSError* error = nil;
190  NSData* data = [putRequest send:&error];
191  NSString* result = [[NSString alloc] initWithData:data
192                                           encoding:NSUTF8StringEncoding];
193  int responseCode = [[putRequest response] statusCode];
194  [putRequest release];
195
196  if (error || responseCode != 200) {
197    fprintf(stdout, "Failed to upload symbol file.\n");
198    fprintf(stdout, "Response code: %d\n", responseCode);
199    fprintf(stdout, "Response:\n");
200    fprintf(stdout, "%s\n", [result UTF8String]);
201    return;
202  }
203
204  CompleteUploadResult completeUploadResult =
205      [SymbolCollectorClient completeUploadOnServer:options->uploadURLStr
206                                         withAPIKey:options->apiKey
207                                      withUploadKey:[URLResponse uploadKey]
208                                      withDebugFile:debugFile
209                                        withDebugID:debugID
210                                           withType:options->type];
211  [URLResponse release];
212  if (completeUploadResult == CompleteUploadResultError) {
213    fprintf(stdout, "Failed to complete upload.\n");
214    return;
215  } else if (completeUploadResult == CompleteUploadResultDuplicateData) {
216    fprintf(stdout, "Uploaded file checksum matched existing file checksum,"
217                    " no change necessary.\n");
218  } else {
219    fprintf(stdout, "Successfully sent the symbol file.\n");
220  }
221  options->result = kResultSuccess;
222}
223
224//=============================================================================
225static void Start(Options* options) {
226  // If non-BREAKPAD upload special-case.
227  if (![options->type isEqualToString:kBreakpadSymbolType]) {
228    StartSymUploadProtocolV2(options, options->debugID, options->codeFile);
229    return;
230  }
231
232  NSArray* moduleParts = ModuleDataForSymbolFile(options->symbolsPath);
233  // MODULE <os> <cpu> <uuid> <module-name>
234  // 0      1    2     3      4
235  NSString* OS = [moduleParts objectAtIndex:1];
236  NSString* CPU = [moduleParts objectAtIndex:2];
237  NSMutableString* debugID =
238      [NSMutableString stringWithString:[moduleParts objectAtIndex:3]];
239  [debugID replaceOccurrencesOfString:@"-"
240                           withString:@""
241                              options:0
242                                range:NSMakeRange(0, [debugID length])];
243  NSString* debugFile = [moduleParts objectAtIndex:4];
244
245  if (options->symUploadProtocol == kSymUploadProtocolV1) {
246    StartSymUploadProtocolV1(options, OS, CPU, debugID, debugFile);
247  } else if (options->symUploadProtocol == kSymUploadProtocolV2) {
248    StartSymUploadProtocolV2(options, debugID, debugFile);
249  }
250}
251
252//=============================================================================
253static void Usage(int argc, const char* argv[]) {
254  fprintf(stderr, "Submit symbol information.\n");
255  fprintf(stderr, "Usage: %s [options] <symbol-file> <upload-URL>\n", argv[0]);
256  fprintf(stderr, "<symbol-file> should be created by using the dump_syms "
257                  "tool.\n");
258  fprintf(stderr, "<upload-URL> is the destination for the upload.\n");
259  fprintf(stderr, "Options:\n");
260  fprintf(stderr, "\t-p <protocol>: protocol to use for upload, accepts "
261                  "[\"sym-upload-v1\", \"sym-upload-v2\"]. Default is "
262                  "\"sym-upload-v1\".\n");
263  fprintf(stderr, "\t-k <api-key>: secret for authentication with upload "
264                  "server. [Only in sym-upload-v2 protocol mode]\n");
265  fprintf(stderr, "\t-f: Overwrite symbol file on server if already present. "
266                  "[Only in sym-upload-v2 protocol mode]\n");
267  fprintf(
268      stderr,
269      "-t:\t <symbol-type> Explicitly set symbol upload type ("
270      "default is 'breakpad').\n"
271      "\t One of ['breakpad', 'elf', 'pe', 'macho', 'debug_only', 'dwp', "
272      "'dsym', 'pdb'].\n"
273      "\t Note: When this flag is set to anything other than 'breakpad', then "
274      "the '-c' and '-i' flags must also be set.\n");
275  fprintf(stderr, "-c:\t <code-file> Explicitly set 'code_file' for symbol "
276                  "upload (basename of executable).\n");
277  fprintf(stderr, "-i:\t <debug-id> Explicitly set 'debug_id' for symbol "
278                  "upload (typically build ID of executable).\n");
279  fprintf(stderr, "\t-h: Usage\n");
280  fprintf(stderr, "\t-?: Usage\n");
281  fprintf(stderr, "\n");
282  fprintf(stderr, "Exit codes:\n");
283  fprintf(stderr, "\t%d: Success\n", kResultSuccess);
284  fprintf(stderr, "\t%d: Failure\n", kResultFailure);
285  fprintf(stderr,
286          "\t%d: Symbol file already exists on server (and -f was not "
287          "specified).\n",
288          kResultAlreadyExists);
289  fprintf(stderr,
290          "\t   [This exit code will only be returned by the sym-upload-v2 "
291          "protocol.\n");
292  fprintf(stderr,
293          "\t    The sym-upload-v1 protocol can return either Success or "
294          "Failure\n");
295  fprintf(stderr, "\t    in this case, and the action taken by the server is "
296                  "unspecified.]\n");
297  fprintf(stderr, "\n");
298  fprintf(stderr, "Examples:\n");
299  fprintf(stderr, "  With 'sym-upload-v1':\n");
300  fprintf(stderr, "    %s path/to/symbol_file http://myuploadserver\n",
301          argv[0]);
302  fprintf(stderr, "  With 'sym-upload-v2':\n");
303  fprintf(stderr, "    [Defaulting to symbol type 'BREAKPAD']\n");
304  fprintf(stderr,
305          "    %s -p sym-upload-v2 -k mysecret123! "
306          "path/to/symbol_file http://myuploadserver\n",
307          argv[0]);
308  fprintf(stderr, "    [Explicitly set symbol type to 'macho']\n");
309  fprintf(stderr,
310          "    %s -p sym-upload-v2 -k mysecret123! -t macho "
311          "-c app -i 11111111BBBB3333DDDD555555555555F "
312          "path/to/symbol_file http://myuploadserver\n",
313          argv[0]);
314}
315
316//=============================================================================
317static void SetupOptions(int argc, const char* argv[], Options* options) {
318  // Set default options values.
319  options->symUploadProtocol = kSymUploadProtocolV1;
320  options->apiKey = nil;
321  options->type = kBreakpadSymbolType;
322  options->codeFile = nil;
323  options->debugID = nil;
324  options->force = NO;
325
326  extern int optind;
327  char ch;
328
329  while ((ch = getopt(argc, (char* const*)argv, "p:k:t:c:i:hf?")) != -1) {
330    switch (ch) {
331      case 'p':
332        if (strcmp(optarg, "sym-upload-v2") == 0) {
333          options->symUploadProtocol = kSymUploadProtocolV2;
334          break;
335        } else if (strcmp(optarg, "sym-upload-v1") == 0) {
336          // This is already the default but leave in case that changes.
337          options->symUploadProtocol = kSymUploadProtocolV1;
338          break;
339        }
340        Usage(argc, argv);
341        exit(0);
342        break;
343      case 'k':
344        options->apiKey = [NSString stringWithCString:optarg
345                                             encoding:NSASCIIStringEncoding];
346        break;
347      case 't': {
348        // This is really an enum, so treat as upper-case for consistency with
349        // enum naming convention on server-side.
350        options->type = [[NSString stringWithCString:optarg
351                                            encoding:NSASCIIStringEncoding]
352            uppercaseString];
353        break;
354      }
355      case 'c':
356        options->codeFile = [NSString stringWithCString:optarg
357                                               encoding:NSASCIIStringEncoding];
358        ;
359        break;
360      case 'i':
361        options->debugID = [NSString stringWithCString:optarg
362                                              encoding:NSASCIIStringEncoding];
363        ;
364        break;
365      case 'f':
366        options->force = YES;
367        break;
368      default:
369        Usage(argc, argv);
370        exit(0);
371        break;
372    }
373  }
374
375  if ((argc - optind) != 2) {
376    fprintf(stderr, "%s: Missing symbols file and/or upload-URL\n", argv[0]);
377    Usage(argc, argv);
378    exit(1);
379  }
380
381  int fd = open(argv[optind], O_RDONLY);
382  if (fd < 0) {
383    fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
384    exit(1);
385  }
386
387  struct stat statbuf;
388  if (fstat(fd, &statbuf) < 0) {
389    fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
390    close(fd);
391    exit(1);
392  }
393  close(fd);
394
395  if (!S_ISREG(statbuf.st_mode)) {
396    fprintf(stderr, "%s: %s: not a regular file\n", argv[0], argv[optind]);
397    exit(1);
398  }
399
400  bool isBreakpadUpload = [options->type isEqualToString:kBreakpadSymbolType];
401  bool hasCodeFile = options->codeFile != nil;
402  bool hasDebugID = options->debugID != nil;
403  if (isBreakpadUpload && (hasCodeFile || hasDebugID)) {
404    fprintf(stderr, "\n");
405    fprintf(stderr,
406            "%s: -c and -i should only be specified for non-breakpad "
407            "symbol upload types.\n",
408            argv[0]);
409    fprintf(stderr, "\n");
410    Usage(argc, argv);
411    exit(1);
412  }
413  if (!isBreakpadUpload && (!hasCodeFile || !hasDebugID)) {
414    fprintf(stderr, "\n");
415    fprintf(stderr,
416            "%s: -c and -i must be specified for non-breakpad "
417            "symbol upload types.\n",
418            argv[0]);
419    fprintf(stderr, "\n");
420    Usage(argc, argv);
421    exit(1);
422  }
423
424  options->symbolsPath = [NSString stringWithUTF8String:argv[optind]];
425  options->uploadURLStr = [NSString stringWithUTF8String:argv[optind + 1]];
426}
427
428//=============================================================================
429int main(int argc, const char* argv[]) {
430  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
431  Options options;
432
433  bzero(&options, sizeof(Options));
434  SetupOptions(argc, argv, &options);
435  Start(&options);
436
437  [pool release];
438  return options.result;
439}
440