blob: 2260a01a418ff2f600f0445ee4d1ff257789f7b4 [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Upload all debug symbols required for crash reporting purposes.
6
7This script need only be used to upload release builds symbols or to debug
8crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -05009for those executables involved).
10"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040011
Mike Frysingera4fa1e82014-01-15 01:45:56 -050012from __future__ import print_function
13
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040014import ctypes
Mike Frysinger02e92402013-11-22 16:22:02 -050015import functools
Mike Frysingera4fa1e82014-01-15 01:45:56 -050016import httplib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040017import multiprocessing
18import os
Mike Frysinger094a2172013-08-14 12:54:35 -040019import poster
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040020import random
21import textwrap
22import tempfile
23import time
Mike Frysinger094a2172013-08-14 12:54:35 -040024import urllib2
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040025
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040026from chromite.lib import commandline
27from chromite.lib import cros_build_lib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040028from chromite.lib import parallel
Mike Frysinger69cb41d2013-08-11 20:08:19 -040029from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040030
31
32# URLs used for uploading symbols.
33OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
34STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
35
36
37# The crash server rejects files that are this big.
38CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
39# Give ourselves a little breathing room from what the server expects.
40DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
41
42
Mike Frysingercd78a082013-06-26 17:13:04 -040043# How long to wait (in seconds) for a single upload to complete. This has
44# to allow for symbols that are up to CRASH_SERVER_FILE_LIMIT in size.
45UPLOAD_TIMEOUT = 30 * 60
46
47
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040048# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
49DEFAULT_SLEEP_DELAY = 0.2
50
51
52# Number of seconds to wait before retrying an upload. The delay will double
53# for each subsequent retry of the same symbol file.
54INITIAL_RETRY_DELAY = 1
55
56# Allow up to 7 attempts to upload a symbol file (total delay may be
57# 1+2+4+8+16+32=63 seconds).
58MAX_RETRIES = 6
59
Mike Frysingereb753bf2013-11-22 16:05:35 -050060# Number of total errors, before uploads are no longer attempted.
61# This is used to avoid lots of errors causing unreasonable delays.
62# See the related, but independent, error values below.
63MAX_TOTAL_ERRORS_FOR_RETRY = 30
64
65# A watermark of transient errors which we allow recovery from. If we hit
66# errors infrequently, overall we're probably doing fine. For example, if
67# we have one failure every 100 passes, then we probably don't want to fail
68# right away. But if we hit a string of failures in a row, we want to abort.
69#
70# The watermark starts at 0 (and can never go below that). When this error
71# level is exceeded, we stop uploading. When a failure happens, we add the
72# fail adjustment, and when an upload succeeds, we add the pass adjustment.
73# We want to penalize failures more so that we ramp up when there is a string
74# of them, but then slowly back off as things start working.
75#
76# A quick example:
77# 0.0: Starting point.
78# 0.0: Upload works, so add -0.5, and then clamp to 0.
79# 1.0: Upload fails, so add 1.0.
80# 2.0: Upload fails, so add 1.0.
81# 1.5: Upload works, so add -0.5.
82# 1.0: Upload works, so add -0.5.
83ERROR_WATERMARK = 3.0
84ERROR_ADJUST_FAIL = 1.0
85ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040086
87
88def SymUpload(sym_file, upload_url):
Mike Frysinger094a2172013-08-14 12:54:35 -040089 """Upload a symbol file to a HTTP server
90
91 The upload is a multipart/form-data POST with the following parameters:
92 code_file: the basename of the module, e.g. "app"
93 code_identifier: the module file's identifier
94 debug_file: the basename of the debugging file, e.g. "app"
95 debug_identifier: the debug file's identifier, usually consisting of
96 the guid and age embedded in the pdb, e.g.
97 "11111111BBBB3333DDDD555555555555F"
98 version: the file version of the module, e.g. "1.2.3.4"
99 product: HTTP-friendly product name
100 os: the operating system that the module was built for
101 cpu: the CPU that the module was built for
102 symbol_file: the contents of the breakpad-format symbol file
103
104 Args:
105 sym_file: The symbol file to upload
106 upload_url: The crash URL to POST the |sym_file| to
107 """
108 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
109
110 fields = (
111 ('code_file', sym_header.name),
112 ('debug_file', sym_header.name),
113 ('debug_identifier', sym_header.id.replace('-', '')),
114 # Should we set these fields? They aren't critical, but it might be nice?
115 # We'd have to figure out what file this symbol is coming from and what
116 # package provides it ...
117 #('version', None),
118 #('product', 'ChromeOS'),
119 ('os', sym_header.os),
120 ('cpu', sym_header.cpu),
121 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
122 )
123
124 data, headers = poster.encode.multipart_encode(fields)
125 request = urllib2.Request(upload_url, data, headers)
126 request.add_header('User-agent', 'chromite.upload_symbols')
127 urllib2.urlopen(request, timeout=UPLOAD_TIMEOUT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400128
129
130def TestingSymUpload(sym_file, upload_url):
131 """A stub version of SymUpload for --testing usage"""
132 cmd = ['sym_upload', sym_file, upload_url]
133 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
134 returncode = random.randint(1, 100) <= 80
135 cros_build_lib.Debug('would run (and return %i): %s', returncode,
Matt Tennant7feda352013-12-20 14:03:40 -0800136 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400137 if returncode:
138 output = 'Failed to send the symbol file.'
139 else:
140 output = 'Successfully sent the symbol file.'
141 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
142 returncode=returncode)
143 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500144 exceptions = (
145 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
146 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
147 {}, None),
148 urllib2.URLError('[URLError] forced test fail'),
149 )
150 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400151 else:
152 return result
153
154
Mike Frysingereb753bf2013-11-22 16:05:35 -0500155def ErrorLimitHit(num_errors, watermark_errors):
156 """See if our error limit has been hit
157
158 Args:
159 num_errors: A multiprocessing.Value of the raw number of failures.
160 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500161
Mike Frysingereb753bf2013-11-22 16:05:35 -0500162 Returns:
163 True if our error limits have been exceeded.
164 """
165 return ((num_errors is not None and
166 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
167 (watermark_errors is not None and
168 watermark_errors.value > ERROR_WATERMARK))
169
170
171def _UpdateCounter(counter, adj):
172 """Update |counter| by |adj|
173
174 Handle atomic updates of |counter|. Also make sure it does not
175 fall below 0.
176
177 Args:
178 counter: A multiprocessing.Value to update
179 adj: The value to add to |counter|
180 """
181 def _Update():
182 clamp = 0 if type(adj) is int else 0.0
183 counter.value = max(clamp, counter.value + adj)
184
185 if hasattr(counter, 'get_lock'):
186 with counter.get_lock():
187 _Update()
188 elif counter is not None:
189 _Update()
190
191
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400192def UploadSymbol(sym_file, upload_url, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500193 sleep=0, num_errors=None, watermark_errors=None,
194 failed_queue=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400195 """Upload |sym_file| to |upload_url|
196
197 Args:
198 sym_file: The full path to the breakpad symbol to upload
199 upload_url: The crash server to upload things to
200 file_limit: The max file size of a symbol file before we try to strip it
201 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400202 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500203 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500204 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500205
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400206 Returns:
207 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400208 """
209 if num_errors is None:
210 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500211 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400212 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
213 return 0
214
215 upload_file = sym_file
216
217 if sleep:
218 # Keeps us from DoS-ing the symbol server.
219 time.sleep(sleep)
220
221 cros_build_lib.Debug('uploading %s' % sym_file)
222
223 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
224 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
225 bufsize=0) as temp_sym_file:
226 if file_limit:
227 # If the symbols size is too big, strip out the call frame info. The CFI
228 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
229 # all of ours have) and it accounts for over half the size of the symbols
230 # uploaded.
231 file_size = os.path.getsize(sym_file)
232 if file_size > file_limit:
233 cros_build_lib.Warning('stripping CFI from %s due to size %s > %s',
234 sym_file, file_size, file_limit)
235 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
236 if not x.startswith('STACK CFI')])
237 upload_file = temp_sym_file.name
238
239 # Hopefully the crash server will let it through. But it probably won't.
240 # Not sure what the best answer is in this case.
241 file_size = os.path.getsize(upload_file)
242 if file_size > CRASH_SERVER_FILE_LIMIT:
243 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger02e92402013-11-22 16:22:02 -0500244 cros_build_lib.Warning('upload file %s is awfully large, risking '
245 'rejection by the symbol server (%s > %s)',
246 sym_file, file_size, CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400247
248 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500249 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400250 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400251 cros_build_lib.TimedCommand(
252 cros_build_lib.RetryException,
253 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
254 upload_file, upload_url, sleep=INITIAL_RETRY_DELAY,
255 timed_log_msg='upload of %10i bytes took %%s: %s' %
256 (file_size, os.path.basename(sym_file)))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500257 success = True
Mike Frysinger094a2172013-08-14 12:54:35 -0400258 except urllib2.HTTPError as e:
259 cros_build_lib.Warning('could not upload: %s: HTTP %s: %s',
260 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500261 except (urllib2.URLError, httplib.HTTPException) as e:
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400262 cros_build_lib.Warning('could not upload: %s: %s',
263 os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500264 finally:
265 if success:
266 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
267 else:
268 _UpdateCounter(num_errors, 1)
269 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500270 if failed_queue:
271 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400272
273 return num_errors.value
274
275
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500276def SymbolFinder(paths):
277 """Locate symbol files in |paths|
278
279 Args:
280 paths: A list of input paths to walk. Files are returned w/out any checks.
281 Dirs are searched for files that end in ".sym".
Mike Frysinger1a736a82013-12-12 01:50:59 -0500282
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500283 Returns:
284 Yield every viable sym file.
285 """
286 for p in paths:
287 if os.path.isdir(p):
288 for root, _, files in os.walk(p):
289 for f in files:
290 if f.endswith('.sym'):
291 yield os.path.join(root, f)
292 else:
293 yield p
294
295
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400296def UploadSymbols(board=None, official=False, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400297 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger02e92402013-11-22 16:22:02 -0500298 upload_count=None, sym_paths=None, root=None, retry=True):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400299 """Upload all the generated symbols for |board| to the crash server
300
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400301 You can use in a few ways:
302 * pass |board| to locate all of its symbols
303 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500304 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400305
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400306 Args:
307 board: The board whose symbols we wish to upload
308 official: Use the official symbol server rather than the staging one
309 breakpad_dir: The full path to the breakpad directory where symbols live
310 file_limit: The max file size of a symbol file before we try to strip it
311 sleep: How long to sleep in between uploads
312 upload_count: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500313 sym_paths: Specific symbol files (or dirs of sym files) to upload,
314 otherwise search |breakpad_dir|
Mike Frysinger118d2502013-08-19 03:36:56 -0400315 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500316 retry: Whether we should retry failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500317
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400318 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400319 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400320 """
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400321 if official:
322 upload_url = OFFICIAL_UPLOAD_URL
323 else:
324 cros_build_lib.Warning('unofficial builds upload to the staging server')
325 upload_url = STAGING_UPLOAD_URL
326
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500327 if sym_paths:
328 cros_build_lib.Info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400329 else:
330 if breakpad_dir is None:
Mike Frysinger118d2502013-08-19 03:36:56 -0400331 breakpad_dir = os.path.join(
332 root,
333 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400334 cros_build_lib.Info('uploading all symbols to %s from %s', upload_url,
335 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500336 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400337
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400338 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500339 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500340 failed_queue = multiprocessing.Queue()
341 uploader = functools.partial(
342 UploadSymbol, file_limit=file_limit, sleep=sleep, num_errors=bg_errors,
343 watermark_errors=watermark_errors, failed_queue=failed_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400344
Mike Frysinger02e92402013-11-22 16:22:02 -0500345 # For the first run, we collect the symbols that failed. If the
346 # overall failure rate was low, we'll retry them on the second run.
347 for retry in (retry, False):
348 # We need to limit ourselves to one upload at a time to avoid the server
349 # kicking in DoS protection. See these bugs for more details:
350 # http://crbug.com/209442
351 # http://crbug.com/212496
352 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
353 for sym_file in SymbolFinder(sym_paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500354 if upload_count == 0:
355 break
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400356
Mike Frysinger02e92402013-11-22 16:22:02 -0500357 queue.put([sym_file, upload_url])
358
359 if upload_count is not None:
360 upload_count -= 1
361 if upload_count == 0:
362 break
363
364 # See if we need to retry, and if we haven't failed too many times already.
365 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
366 break
367
368 sym_paths = []
369 while not failed_queue.empty():
370 sym_paths.append(failed_queue.get())
371 if sym_paths:
372 cros_build_lib.Warning('retrying %i symbols', len(sym_paths))
Mike Frysinger0d0f9172013-12-13 15:55:40 -0500373 if upload_count is not None:
374 upload_count += len(sym_paths)
Mike Frysinger02e92402013-11-22 16:22:02 -0500375 # Decrement the error count in case we recover in the second pass.
376 assert bg_errors.value >= len(sym_paths), 'more failed files than errors?'
377 bg_errors.value -= len(sym_paths)
378 else:
379 # No failed symbols, so just return now.
380 break
381
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500382 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400383
384
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400385def main(argv):
386 parser = commandline.ArgumentParser(description=__doc__)
387
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500388 parser.add_argument('sym_paths', type='path', nargs='*', default=None)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400389 parser.add_argument('--board', default=None,
390 help='board to build packages for')
391 parser.add_argument('--breakpad_root', type='path', default=None,
392 help='root directory for breakpad symbols')
393 parser.add_argument('--official_build', action='store_true', default=False,
394 help='point to official symbol server')
395 parser.add_argument('--regenerate', action='store_true', default=False,
396 help='regenerate all symbols')
397 parser.add_argument('--upload-count', type=int, default=None,
398 help='only upload # number of symbols')
399 parser.add_argument('--strip_cfi', type=int,
400 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
401 help='strip CFI data for files above this size')
402 parser.add_argument('--testing', action='store_true', default=False,
403 help='run in testing mode')
404 parser.add_argument('--yes', action='store_true', default=False,
405 help='answer yes to all prompts')
406
407 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500408 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400409
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500410 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400411 if opts.regenerate:
412 cros_build_lib.Die('--regenerate may not be used with specific files')
413 else:
414 if opts.board is None:
415 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400416
417 if opts.breakpad_root and opts.regenerate:
418 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
419
420 if opts.testing:
421 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
422 cros_build_lib.Info('running in testing mode')
423 # pylint: disable=W0601,W0603
424 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
425 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
426 SymUpload = TestingSymUpload
427
428 if not opts.yes:
429 query = textwrap.wrap(textwrap.dedent("""
430 Uploading symbols for an entire Chromium OS build is really only
431 necessary for release builds and in a few cases for developers
432 to debug problems. It will take considerable time to run. For
433 developer debugging purposes, consider instead passing specific
434 files to upload.
435 """), 80)
436 cros_build_lib.Warning('\n%s', '\n'.join(query))
437 if not cros_build_lib.BooleanPrompt(
438 prompt='Are you sure you want to upload all build symbols',
439 default=False):
440 cros_build_lib.Die('better safe than sorry')
441
442 ret = 0
443 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400444 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
445 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400446
447 ret += UploadSymbols(opts.board, official=opts.official_build,
448 breakpad_dir=opts.breakpad_root,
449 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500450 upload_count=opts.upload_count, sym_paths=opts.sym_paths)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400451 if ret:
452 cros_build_lib.Error('encountered %i problem(s)', ret)
453 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
454 # return 0 in case we are a multiple of the mask.
455 ret = 1
456
457 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400458
459
460# We need this to run once per process. Do it at module import time as that
461# will let us avoid doing it inline at function call time (see SymUpload) as
462# that func might be called by the multiprocessing module which means we'll
463# do the opener logic multiple times overall. Plus, if you're importing this
464# module, it's a pretty good chance that you're going to need this.
465poster.streaminghttp.register_openers()