blob: 434c3b0f56b03742a1ae11e28b5150d79b860ec0 [file] [log] [blame]
Brian Harringb938c782012-02-29 15:14:38 -08001#!/usr/bin/env python
Mike Frysinger2de7f042012-07-10 04:45:03 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This script fetches and prepares an SDK chroot.
7"""
8
Josh Triplett472a4182013-03-08 11:48:57 -08009import glob
Brian Harringb938c782012-02-29 15:14:38 -080010import os
Josh Triplett472a4182013-03-08 11:48:57 -080011import pwd
David James56e6c2c2012-10-24 23:54:41 -070012import sys
Brian Harringb938c782012-02-29 15:14:38 -080013import urlparse
14
15from chromite.buildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080016from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070017from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080018from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080019from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080020from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070021from chromite.lib import osutils
Mike Frysinger8e727a32013-01-16 16:57:53 -050022from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080023
24cros_build_lib.STRICT_SUDO = True
25
26
Zdenek Behanaa52cea2012-05-30 01:31:11 +020027COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020028
Brian Harringb938c782012-02-29 15:14:38 -080029# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050030MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
31 'src/scripts/sdk_lib/make_chroot.sh')]
32ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
33 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080034
Josh Triplett472a4182013-03-08 11:48:57 -080035# Proxy simulator configuration.
36PROXY_HOST_IP = '192.168.240.1'
37PROXY_PORT = 8080
38PROXY_GUEST_IP = '192.168.240.2'
39PROXY_NETMASK = 30
40PROXY_VETH_PREFIX = 'veth'
41PROXY_CONNECT_PORTS = (80, 443, 9418)
42PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
43PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
44PROXY_APACHE_FALLBACK_PATH = ':'.join(
45 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
46)
47PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
48
Josh Triplett9a495f62013-03-15 18:06:55 -070049# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080050NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080051
Josh Triplett472a4182013-03-08 11:48:57 -080052# Tools needed for --proxy-sim only.
53PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080054
Brian Harring1790ac42012-09-23 08:53:33 -070055def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080056 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070057 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050058 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
59 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070060 for compressor in COMPRESSION_PREFERENCE]
61
62
63def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050064 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070065 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080066
67
Brian Harringae0a5322012-09-15 01:46:51 -070068def FetchRemoteTarballs(storage_dir, urls):
Zdenek Behanfd0efe42012-04-13 04:36:40 +020069 """Fetches a tarball given by url, and place it in sdk/.
70
71 Args:
72 urls: List of URLs to try to download. Download will stop on first success.
73
74 Returns:
75 Full path to the downloaded file
76 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020077
Brian Harring1790ac42012-09-23 08:53:33 -070078 # Note we track content length ourselves since certain versions of curl
79 # fail if asked to resume a complete file.
80 # pylint: disable=C0301,W0631
81 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020082 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070083 # http://www.logilab.org/ticket/8766
84 # pylint: disable=E1101
85 parsed = urlparse.urlparse(url)
86 tarball_name = os.path.basename(parsed.path)
87 if parsed.scheme in ('', 'file'):
88 if os.path.exists(parsed.path):
89 return parsed.path
90 continue
91 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020092 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -070093 result = cros_build_lib.RunCurl(
94 ['-I', url], redirect_stdout=True, redirect_stderr=True,
95 print_cmd=False)
96 successful = False
97 for header in result.output.splitlines():
98 # We must walk the output to find the string '200 OK' for use cases where
99 # a proxy is involved and may have pushed down the actual header.
100 if header.find('200 OK') != -1:
101 successful = True
102 elif header.lower().startswith("content-length:"):
103 content_length = int(header.split(":", 1)[-1].strip())
104 if successful:
105 break
106 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200107 break
108 else:
109 raise Exception('No valid URLs found!')
110
Brian Harringae0a5322012-09-15 01:46:51 -0700111 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700112 current_size = 0
113 if os.path.exists(tarball_dest):
114 current_size = os.path.getsize(tarball_dest)
115 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700116 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700117 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100118
Brian Harring1790ac42012-09-23 08:53:33 -0700119 if current_size < content_length:
120 cros_build_lib.RunCurl(
121 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
122 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800123
Brian Harring1790ac42012-09-23 08:53:33 -0700124 # Cleanup old tarballs now since we've successfull fetched; only cleanup
125 # the tarballs for our prefix, or unknown ones.
126 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
127 else 'cros-sdk-')
128 for filename in os.listdir(storage_dir):
129 if filename == tarball_name or filename.startswith(ignored_prefix):
130 continue
Brian Harringb938c782012-02-29 15:14:38 -0800131
Brian Harring1790ac42012-09-23 08:53:33 -0700132 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700133 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200134
Brian Harringb938c782012-02-29 15:14:38 -0800135 return tarball_dest
136
137
Brian Harring1790ac42012-09-23 08:53:33 -0700138def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800139 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800140
Brian Harring1790ac42012-09-23 08:53:33 -0700141 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700142 '--chroot', chroot_path,
143 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400144 if nousepkg:
145 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800146
147 try:
148 cros_build_lib.RunCommand(cmd, print_cmd=False)
149 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700150 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800151
152
153def DeleteChroot(chroot_path):
154 """Deletes an existing chroot"""
155 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
156 '--delete']
157 try:
158 cros_build_lib.RunCommand(cmd, print_cmd=False)
159 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700160 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800161
162
Brian Harringae0a5322012-09-15 01:46:51 -0700163def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
164 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800165 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700166 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800167 if chrome_root:
168 cmd.extend(['--chrome_root', chrome_root])
169 if chrome_root_mount:
170 cmd.extend(['--chrome_root_mount', chrome_root_mount])
171 if len(additional_args) > 0:
172 cmd.append('--')
173 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700174
175 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
176 # If we were in interactive mode, ignore the exit code; it'll be whatever
177 # they last ran w/in the chroot and won't matter to us one way or another.
178 # Note this does allow chroot entrance to fail and be ignored during
179 # interactive; this is however a rare case and the user will immediately
180 # see it (nor will they be checking the exit code manually).
181 if ret.returncode != 0 and additional_args:
182 raise SystemExit('Running %r failed with exit code %i'
183 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800184
185
David James56e6c2c2012-10-24 23:54:41 -0700186def _SudoCommand():
187 """Get the 'sudo' command, along with all needed environment variables."""
188
David James5a73b4d2013-03-07 10:23:40 -0800189 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
190 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700191 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800192 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700193 value = os.environ.get(key)
194 if value is not None:
195 cmd += ['%s=%s' % (key, value)]
196
197 # Pass in the path to the depot_tools so that users can access them from
198 # within the chroot.
199 gclient = osutils.Which('gclient')
200 if gclient is not None:
201 cmd += ['DEPOT_TOOLS=%s' % os.path.realpath(os.path.dirname(gclient))]
202
203 return cmd
204
205
Josh Triplett472a4182013-03-08 11:48:57 -0800206def _ReportMissing(missing):
207 """Report missing utilities, then exit.
208
209 Args:
210 missing: List of missing utilities, as returned by
211 osutils.FindMissingBinaries. If non-empty, will not return.
212 """
213
214 if missing:
215 raise SystemExit(
216 'The tool(s) %s were not found.\n'
217 'Please install the appropriate package in your host.\n'
218 'Example(ubuntu):\n'
219 ' sudo apt-get install <packagename>'
220 % ', '.join(missing))
221
222
223def _ProxySimSetup(options):
224 """Set up proxy simulator, and return only in the child environment.
225
226 TODO: Ideally, this should support multiple concurrent invocations of
227 cros_sdk --proxy-sim; currently, such invocations will conflict with each
228 other due to the veth device names and IP addresses. Either this code would
229 need to generate fresh, unused names for all of these before forking, or it
230 would need to support multiple concurrent cros_sdk invocations sharing one
231 proxy and allowing it to exit when unused (without counting on any local
232 service-management infrastructure on the host).
233 """
234
235 may_need_mpm = False
236 apache_bin = osutils.Which('apache2')
237 if apache_bin is None:
238 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
239 if apache_bin is None:
240 _ReportMissing(('apache2',))
241 else:
242 may_need_mpm = True
243
244 # Module names and .so names included for ease of grepping.
245 apache_modules = [('proxy_module', 'mod_proxy.so'),
246 ('proxy_connect_module', 'mod_proxy_connect.so'),
247 ('proxy_http_module', 'mod_proxy_http.so'),
248 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
249
250 # Find the apache module directory, and make sure it has the modules we need.
251 module_dirs = {}
252 for g in PROXY_APACHE_MODULE_GLOBS:
253 for mod, so in apache_modules:
254 for f in glob.glob(os.path.join(g, so)):
255 module_dirs.setdefault(os.path.dirname(f), []).append(so)
256 for apache_module_path, modules_found in module_dirs.iteritems():
257 if len(modules_found) == len(apache_modules):
258 break
259 else:
260 # Appease cros lint, which doesn't understand that this else block will not
261 # fall through to the subsequent code which relies on apache_module_path.
262 apache_module_path = None
263 raise SystemExit(
264 'Could not find apache module path containing all required modules: %s'
265 % ', '.join(so for mod, so in apache_modules))
266
267 def check_add_module(name):
268 so = 'mod_%s.so' % name
269 if os.access(os.path.join(apache_module_path, so), os.F_OK):
270 mod = '%s_module' % name
271 apache_modules.append((mod, so))
272 return True
273 return False
274
275 check_add_module('authz_core')
276 if may_need_mpm:
277 for mpm in PROXY_APACHE_MPMS:
278 if check_add_module('mpm_%s' % mpm):
279 break
280
281 veth_host = '%s-host' % PROXY_VETH_PREFIX
282 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
283
284 # Set up pipes from parent to child and vice versa.
285 # The child writes a byte to the parent after calling unshare, so that the
286 # parent can then assign the guest end of the veth interface to the child's
287 # new network namespace. The parent then writes a byte to the child after
288 # assigning the guest interface, so that the child can then configure that
289 # interface. In both cases, if we get back an EOF when reading from the
290 # pipe, we assume the other end exited with an error message, so just exit.
291 parent_readfd, child_writefd = os.pipe()
292 child_readfd, parent_writefd = os.pipe()
293 SUCCESS_FLAG = '+'
294
295 pid = os.fork()
296 if not pid:
297 os.close(parent_readfd)
298 os.close(parent_writefd)
299
300 namespaces.Unshare(namespaces.CLONE_NEWNET)
301 os.write(child_writefd, SUCCESS_FLAG)
302 os.close(child_writefd)
303 if os.read(child_readfd, 1) != SUCCESS_FLAG:
304 # Parent failed; it will have already have outputted an error message.
305 sys.exit(1)
306 os.close(child_readfd)
307
308 # Set up child side of the network.
309 commands = (
310 ('ip', 'address', 'add',
311 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
312 'dev', veth_guest),
313 ('ip', 'link', 'set', veth_guest, 'up'),
314 )
315 try:
316 for cmd in commands:
317 cros_build_lib.RunCommand(cmd, print_cmd=False)
318 except cros_build_lib.RunCommandError:
319 raise SystemExit('Running %r failed!' % (cmd,))
320
321 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
322 for proto in ('http', 'https', 'ftp'):
323 os.environ[proto + '_proxy'] = proxy_url
324 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
325 os.environ.pop(v, None)
326 return
327
328 os.close(child_readfd)
329 os.close(child_writefd)
330
331 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
332 # Child failed; it will have already have outputted an error message.
333 sys.exit(1)
334 os.close(parent_readfd)
335
336 # Set up parent side of the network.
337 uid = int(os.environ.get('SUDO_UID', '0'))
338 gid = int(os.environ.get('SUDO_GID', '0'))
339 if uid == 0 or gid == 0:
340 for username in PROXY_APACHE_FALLBACK_USERS:
341 try:
342 pwnam = pwd.getpwnam(username)
343 uid, gid = pwnam.pw_uid, pwnam.pw_gid
344 break
345 except KeyError:
346 continue
347 if uid == 0 or gid == 0:
348 raise SystemExit('Could not find a non-root user to run Apache as')
349
350 chroot_parent, chroot_base = os.path.split(options.chroot)
351 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
352 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
353
354 apache_directives = [
355 'User #%u' % uid,
356 'Group #%u' % gid,
357 'PidFile %s' % pid_file,
358 'ErrorLog %s' % log_file,
359 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
360 'ServerName %s' % PROXY_HOST_IP,
361 'ProxyRequests On',
362 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
363 ] + [
364 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
365 for (mod, so) in apache_modules
366 ]
367 commands = (
368 ('ip', 'link', 'add', 'name', veth_host,
369 'type', 'veth', 'peer', 'name', veth_guest),
370 ('ip', 'address', 'add',
371 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
372 'dev', veth_host),
373 ('ip', 'link', 'set', veth_host, 'up'),
374 [apache_bin, '-f', '/dev/null']
375 + [arg for d in apache_directives for arg in ('-C', d)],
376 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
377 )
378 cmd = None # Make cros lint happy.
379 try:
380 for cmd in commands:
381 cros_build_lib.RunCommand(cmd, print_cmd=False)
382 except cros_build_lib.RunCommandError:
383 # Clean up existing interfaces, if any.
384 cmd_cleanup = ('ip', 'link', 'del', veth_host)
385 try:
386 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
387 except cros_build_lib.RunCommandError:
388 cros_build_lib.Error('running %r failed', cmd_cleanup)
389 raise SystemExit('Running %r failed!' % (cmd,))
390 os.write(parent_writefd, SUCCESS_FLAG)
391 os.close(parent_writefd)
392
393 sys.exit(os.waitpid(pid, 0)[1])
394
395
Mike Frysingera78a56e2012-11-20 06:02:30 -0500396def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700397 """Re-execute cros_sdk as root.
398
399 Also unshare the mount namespace so as to ensure that processes outside
400 the chroot can't mess with our mounts.
401 """
402 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500403 cmd = _SudoCommand() + ['--'] + argv
404 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500405 else:
Josh Triplette759b232013-03-08 13:03:43 -0800406 cgroups.Cgroup.InitSystem()
407 namespaces.Unshare(namespaces.CLONE_NEWNS)
David James56e6c2c2012-10-24 23:54:41 -0700408
409
Brian Harring6be2efc2012-03-01 05:04:00 -0800410def main(argv):
Brian Harring218e13c2012-10-10 16:21:26 -0700411 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800412
Brian Harring218e13c2012-10-10 16:21:26 -0700413This script is used for manipulating local chroot environments; creating,
414deleting, downloading, etc. If given --enter (or no args), it defaults
415to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800416
Brian Harring218e13c2012-10-10 16:21:26 -0700417If given args those are passed to the chroot environment, and executed."""
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500418 conf = cros_build_lib.LoadKeyValueFile(
419 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
420 ignore_missing=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700421 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
422 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
423
Brian Harring218e13c2012-10-10 16:21:26 -0700424 parser = commandline.OptionParser(usage=usage, caching=True)
425
426 commands = parser.add_option_group("Commands")
427 commands.add_option(
428 '--enter', action='store_true', default=False,
429 help='Enter the SDK chroot. Implies --create.')
430 commands.add_option(
431 '--create', action='store_true',default=False,
432 help='Create the chroot only if it does not already exist. '
433 'Implies --download.')
434 commands.add_option(
435 '--bootstrap', action='store_true', default=False,
436 help='Build everything from scratch, including the sdk. '
437 'Use this only if you need to validate a change '
438 'that affects SDK creation itself (toolchain and '
439 'build are typically the only folk who need this). '
440 'Note this will quite heavily slow down the build. '
441 'This option implies --create --nousepkg.')
442 commands.add_option(
443 '-r', '--replace', action='store_true', default=False,
444 help='Replace an existing SDK chroot. Basically an alias '
445 'for --delete --create.')
446 commands.add_option(
447 '--delete', action='store_true', default=False,
448 help='Delete the current SDK chroot if it exists.')
449 commands.add_option(
450 '--download', action='store_true', default=False,
451 help='Download the sdk.')
Brian Harringb938c782012-02-29 15:14:38 -0800452
453 # Global options:
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500454 default_chroot = os.path.join(constants.SOURCE_ROOT,
455 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700456 parser.add_option(
457 '--chroot', dest='chroot', default=default_chroot, type='path',
458 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800459
Brian Harring218e13c2012-10-10 16:21:26 -0700460 parser.add_option('--chrome_root', default=None, type='path',
461 help='Mount this chrome root into the SDK chroot')
462 parser.add_option('--chrome_root_mount', default=None, type='path',
463 help='Mount chrome into this path inside SDK chroot')
464 parser.add_option('--nousepkg', action='store_true', default=False,
465 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800466 parser.add_option('--proxy-sim', action='store_true', default=False,
467 help='Simulate a restrictive network requiring an outbound'
468 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800469 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700470 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800471 help=('''Use sdk tarball located at this url.
472 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700473 parser.add_option('--sdk-version', default=None,
474 help='Use this sdk version. For prebuilt, current is %r'
475 ', for bootstrapping its %r.'
476 % (sdk_latest_version, bootstrap_latest_version))
Brian Harring218e13c2012-10-10 16:21:26 -0700477 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800478
479 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500480 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800481
Brian Harring1790ac42012-09-23 08:53:33 -0700482 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700483 if host != 'x86_64':
484 parser.error(
485 "cros_sdk is currently only supported on x86_64; you're running"
486 " %s. Please find a x86_64 machine." % (host,))
487
Josh Triplett472a4182013-03-08 11:48:57 -0800488 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
489 if options.proxy_sim:
490 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800491
David James471532c2013-01-21 10:23:31 -0800492 _ReExecuteIfNeeded([sys.argv[0]] + argv)
493
Brian Harring218e13c2012-10-10 16:21:26 -0700494 # Expand out the aliases...
495 if options.replace:
496 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800497
Brian Harring218e13c2012-10-10 16:21:26 -0700498 if options.bootstrap:
499 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800500
Brian Harring218e13c2012-10-10 16:21:26 -0700501 # If a command is not given, default to enter.
502 options.enter |= not any(getattr(options, x.dest)
503 for x in commands.option_list)
504 options.enter |= bool(chroot_command)
505
506 if options.enter and options.delete and not options.create:
507 parser.error("Trying to enter the chroot when --delete "
508 "was specified makes no sense.")
509
510 # Finally, discern if we need to create the chroot.
511 chroot_exists = os.path.exists(options.chroot)
512 if options.create or options.enter:
513 # Only create if it's being wiped, or if it doesn't exist.
514 if not options.delete and chroot_exists:
515 options.create = False
516 else:
517 options.download = True
518
519 # Finally, flip create if necessary.
520 if options.enter:
521 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800522
Brian Harringb938c782012-02-29 15:14:38 -0800523 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700524 sdk_version = (bootstrap_latest_version if options.bootstrap
525 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800526 else:
527 sdk_version = options.sdk_version
528
Brian Harring1790ac42012-09-23 08:53:33 -0700529 # Based on selections, fetch the tarball.
530 if options.sdk_url:
531 urls = [options.sdk_url]
532 elif options.bootstrap:
533 urls = GetStage3Urls(sdk_version)
534 else:
535 urls = GetArchStageTarballs(sdk_version)
536
Brian Harringb6cf9142012-09-01 20:43:17 -0700537 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800538 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700539 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700540 with cgroups.SimpleContainChildren('cros_sdk'):
541 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700542
Josh Triplett472a4182013-03-08 11:48:57 -0800543 if options.proxy_sim:
544 _ProxySimSetup(options)
545
David James56e6c2c2012-10-24 23:54:41 -0700546 if options.delete and os.path.exists(options.chroot):
547 lock.write_lock()
548 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800549
David James56e6c2c2012-10-24 23:54:41 -0700550 sdk_cache = os.path.join(options.cache_dir, 'sdks')
551 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
552 osutils.SafeMakedirs(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700553
David James56e6c2c2012-10-24 23:54:41 -0700554 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500555 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700556 if not os.path.exists(src):
557 osutils.SafeMakedirs(target)
558 continue
559 lock.write_lock(
560 "Upgrade to %r needed but chroot is locked; please exit "
561 "all instances so this upgrade can finish." % src)
562 if not os.path.exists(src):
563 # Note that while waiting for the write lock, src may've vanished;
564 # it's a rare race during the upgrade process that's a byproduct
565 # of us avoiding taking a write lock to do the src check. If we
566 # took a write lock for that check, it would effectively limit
567 # all cros_sdk for a chroot to a single instance.
568 osutils.SafeMakedirs(target)
569 elif not os.path.exists(target):
570 # Upgrade occurred, but a reversion, or something whacky
571 # occurred writing to the old location. Wipe and continue.
572 os.rename(src, target)
573 else:
574 # Upgrade occurred once already, but either a reversion or
575 # some before/after separate cros_sdk usage is at play.
576 # Wipe and continue.
577 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700578
David James56e6c2c2012-10-24 23:54:41 -0700579 if options.download:
580 lock.write_lock()
581 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700582
David James56e6c2c2012-10-24 23:54:41 -0700583 if options.create:
584 lock.write_lock()
585 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
586 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700587
David James56e6c2c2012-10-24 23:54:41 -0700588 if options.enter:
589 lock.read_lock()
590 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
591 options.chrome_root_mount, chroot_command)