blob: 44a3e9bd55d5723f1136c714cd55bb3a0ce17fce [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.
Gaurav Shah9f55ef72013-11-05 11:19:09 -0800199 cmd += ['DEPOT_TOOLS=%s' % osutils.FindDepotTools()]
David James56e6c2c2012-10-24 23:54:41 -0700200 return cmd
201
202
Josh Triplett472a4182013-03-08 11:48:57 -0800203def _ReportMissing(missing):
204 """Report missing utilities, then exit.
205
206 Args:
207 missing: List of missing utilities, as returned by
208 osutils.FindMissingBinaries. If non-empty, will not return.
209 """
210
211 if missing:
212 raise SystemExit(
213 'The tool(s) %s were not found.\n'
214 'Please install the appropriate package in your host.\n'
215 'Example(ubuntu):\n'
216 ' sudo apt-get install <packagename>'
217 % ', '.join(missing))
218
219
220def _ProxySimSetup(options):
221 """Set up proxy simulator, and return only in the child environment.
222
223 TODO: Ideally, this should support multiple concurrent invocations of
224 cros_sdk --proxy-sim; currently, such invocations will conflict with each
225 other due to the veth device names and IP addresses. Either this code would
226 need to generate fresh, unused names for all of these before forking, or it
227 would need to support multiple concurrent cros_sdk invocations sharing one
228 proxy and allowing it to exit when unused (without counting on any local
229 service-management infrastructure on the host).
230 """
231
232 may_need_mpm = False
233 apache_bin = osutils.Which('apache2')
234 if apache_bin is None:
235 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
236 if apache_bin is None:
237 _ReportMissing(('apache2',))
238 else:
239 may_need_mpm = True
240
241 # Module names and .so names included for ease of grepping.
242 apache_modules = [('proxy_module', 'mod_proxy.so'),
243 ('proxy_connect_module', 'mod_proxy_connect.so'),
244 ('proxy_http_module', 'mod_proxy_http.so'),
245 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
246
247 # Find the apache module directory, and make sure it has the modules we need.
248 module_dirs = {}
249 for g in PROXY_APACHE_MODULE_GLOBS:
250 for mod, so in apache_modules:
251 for f in glob.glob(os.path.join(g, so)):
252 module_dirs.setdefault(os.path.dirname(f), []).append(so)
253 for apache_module_path, modules_found in module_dirs.iteritems():
254 if len(modules_found) == len(apache_modules):
255 break
256 else:
257 # Appease cros lint, which doesn't understand that this else block will not
258 # fall through to the subsequent code which relies on apache_module_path.
259 apache_module_path = None
260 raise SystemExit(
261 'Could not find apache module path containing all required modules: %s'
262 % ', '.join(so for mod, so in apache_modules))
263
264 def check_add_module(name):
265 so = 'mod_%s.so' % name
266 if os.access(os.path.join(apache_module_path, so), os.F_OK):
267 mod = '%s_module' % name
268 apache_modules.append((mod, so))
269 return True
270 return False
271
272 check_add_module('authz_core')
273 if may_need_mpm:
274 for mpm in PROXY_APACHE_MPMS:
275 if check_add_module('mpm_%s' % mpm):
276 break
277
278 veth_host = '%s-host' % PROXY_VETH_PREFIX
279 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
280
281 # Set up pipes from parent to child and vice versa.
282 # The child writes a byte to the parent after calling unshare, so that the
283 # parent can then assign the guest end of the veth interface to the child's
284 # new network namespace. The parent then writes a byte to the child after
285 # assigning the guest interface, so that the child can then configure that
286 # interface. In both cases, if we get back an EOF when reading from the
287 # pipe, we assume the other end exited with an error message, so just exit.
288 parent_readfd, child_writefd = os.pipe()
289 child_readfd, parent_writefd = os.pipe()
290 SUCCESS_FLAG = '+'
291
292 pid = os.fork()
293 if not pid:
294 os.close(parent_readfd)
295 os.close(parent_writefd)
296
297 namespaces.Unshare(namespaces.CLONE_NEWNET)
298 os.write(child_writefd, SUCCESS_FLAG)
299 os.close(child_writefd)
300 if os.read(child_readfd, 1) != SUCCESS_FLAG:
301 # Parent failed; it will have already have outputted an error message.
302 sys.exit(1)
303 os.close(child_readfd)
304
305 # Set up child side of the network.
306 commands = (
307 ('ip', 'address', 'add',
308 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
309 'dev', veth_guest),
310 ('ip', 'link', 'set', veth_guest, 'up'),
311 )
312 try:
313 for cmd in commands:
314 cros_build_lib.RunCommand(cmd, print_cmd=False)
315 except cros_build_lib.RunCommandError:
316 raise SystemExit('Running %r failed!' % (cmd,))
317
318 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
319 for proto in ('http', 'https', 'ftp'):
320 os.environ[proto + '_proxy'] = proxy_url
321 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
322 os.environ.pop(v, None)
323 return
324
325 os.close(child_readfd)
326 os.close(child_writefd)
327
328 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
329 # Child failed; it will have already have outputted an error message.
330 sys.exit(1)
331 os.close(parent_readfd)
332
333 # Set up parent side of the network.
334 uid = int(os.environ.get('SUDO_UID', '0'))
335 gid = int(os.environ.get('SUDO_GID', '0'))
336 if uid == 0 or gid == 0:
337 for username in PROXY_APACHE_FALLBACK_USERS:
338 try:
339 pwnam = pwd.getpwnam(username)
340 uid, gid = pwnam.pw_uid, pwnam.pw_gid
341 break
342 except KeyError:
343 continue
344 if uid == 0 or gid == 0:
345 raise SystemExit('Could not find a non-root user to run Apache as')
346
347 chroot_parent, chroot_base = os.path.split(options.chroot)
348 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
349 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
350
351 apache_directives = [
352 'User #%u' % uid,
353 'Group #%u' % gid,
354 'PidFile %s' % pid_file,
355 'ErrorLog %s' % log_file,
356 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
357 'ServerName %s' % PROXY_HOST_IP,
358 'ProxyRequests On',
359 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
360 ] + [
361 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
362 for (mod, so) in apache_modules
363 ]
364 commands = (
365 ('ip', 'link', 'add', 'name', veth_host,
366 'type', 'veth', 'peer', 'name', veth_guest),
367 ('ip', 'address', 'add',
368 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
369 'dev', veth_host),
370 ('ip', 'link', 'set', veth_host, 'up'),
371 [apache_bin, '-f', '/dev/null']
372 + [arg for d in apache_directives for arg in ('-C', d)],
373 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
374 )
375 cmd = None # Make cros lint happy.
376 try:
377 for cmd in commands:
378 cros_build_lib.RunCommand(cmd, print_cmd=False)
379 except cros_build_lib.RunCommandError:
380 # Clean up existing interfaces, if any.
381 cmd_cleanup = ('ip', 'link', 'del', veth_host)
382 try:
383 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
384 except cros_build_lib.RunCommandError:
385 cros_build_lib.Error('running %r failed', cmd_cleanup)
386 raise SystemExit('Running %r failed!' % (cmd,))
387 os.write(parent_writefd, SUCCESS_FLAG)
388 os.close(parent_writefd)
389
390 sys.exit(os.waitpid(pid, 0)[1])
391
392
Mike Frysingera78a56e2012-11-20 06:02:30 -0500393def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700394 """Re-execute cros_sdk as root.
395
396 Also unshare the mount namespace so as to ensure that processes outside
397 the chroot can't mess with our mounts.
398 """
399 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500400 cmd = _SudoCommand() + ['--'] + argv
401 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500402 else:
Josh Triplette759b232013-03-08 13:03:43 -0800403 cgroups.Cgroup.InitSystem()
404 namespaces.Unshare(namespaces.CLONE_NEWNS)
David James56e6c2c2012-10-24 23:54:41 -0700405
406
Brian Harring6be2efc2012-03-01 05:04:00 -0800407def main(argv):
Brian Harring218e13c2012-10-10 16:21:26 -0700408 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800409
Brian Harring218e13c2012-10-10 16:21:26 -0700410This script is used for manipulating local chroot environments; creating,
411deleting, downloading, etc. If given --enter (or no args), it defaults
412to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800413
Brian Harring218e13c2012-10-10 16:21:26 -0700414If given args those are passed to the chroot environment, and executed."""
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500415 conf = cros_build_lib.LoadKeyValueFile(
416 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
417 ignore_missing=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700418 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
419 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
420
Brian Harring218e13c2012-10-10 16:21:26 -0700421 parser = commandline.OptionParser(usage=usage, caching=True)
422
423 commands = parser.add_option_group("Commands")
424 commands.add_option(
425 '--enter', action='store_true', default=False,
426 help='Enter the SDK chroot. Implies --create.')
427 commands.add_option(
428 '--create', action='store_true',default=False,
429 help='Create the chroot only if it does not already exist. '
430 'Implies --download.')
431 commands.add_option(
432 '--bootstrap', action='store_true', default=False,
433 help='Build everything from scratch, including the sdk. '
434 'Use this only if you need to validate a change '
435 'that affects SDK creation itself (toolchain and '
436 'build are typically the only folk who need this). '
437 'Note this will quite heavily slow down the build. '
438 'This option implies --create --nousepkg.')
439 commands.add_option(
440 '-r', '--replace', action='store_true', default=False,
441 help='Replace an existing SDK chroot. Basically an alias '
442 'for --delete --create.')
443 commands.add_option(
444 '--delete', action='store_true', default=False,
445 help='Delete the current SDK chroot if it exists.')
446 commands.add_option(
447 '--download', action='store_true', default=False,
448 help='Download the sdk.')
Brian Harringb938c782012-02-29 15:14:38 -0800449
450 # Global options:
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500451 default_chroot = os.path.join(constants.SOURCE_ROOT,
452 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700453 parser.add_option(
454 '--chroot', dest='chroot', default=default_chroot, type='path',
455 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800456
Brian Harring218e13c2012-10-10 16:21:26 -0700457 parser.add_option('--chrome_root', default=None, type='path',
458 help='Mount this chrome root into the SDK chroot')
459 parser.add_option('--chrome_root_mount', default=None, type='path',
460 help='Mount chrome into this path inside SDK chroot')
461 parser.add_option('--nousepkg', action='store_true', default=False,
462 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800463 parser.add_option('--proxy-sim', action='store_true', default=False,
464 help='Simulate a restrictive network requiring an outbound'
465 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800466 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700467 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800468 help=('''Use sdk tarball located at this url.
469 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700470 parser.add_option('--sdk-version', default=None,
471 help='Use this sdk version. For prebuilt, current is %r'
472 ', for bootstrapping its %r.'
473 % (sdk_latest_version, bootstrap_latest_version))
Brian Harring218e13c2012-10-10 16:21:26 -0700474 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800475
476 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500477 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800478
Brian Harring1790ac42012-09-23 08:53:33 -0700479 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700480 if host != 'x86_64':
481 parser.error(
482 "cros_sdk is currently only supported on x86_64; you're running"
483 " %s. Please find a x86_64 machine." % (host,))
484
Josh Triplett472a4182013-03-08 11:48:57 -0800485 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
486 if options.proxy_sim:
487 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800488
David James471532c2013-01-21 10:23:31 -0800489 _ReExecuteIfNeeded([sys.argv[0]] + argv)
490
Brian Harring218e13c2012-10-10 16:21:26 -0700491 # Expand out the aliases...
492 if options.replace:
493 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800494
Brian Harring218e13c2012-10-10 16:21:26 -0700495 if options.bootstrap:
496 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800497
Brian Harring218e13c2012-10-10 16:21:26 -0700498 # If a command is not given, default to enter.
499 options.enter |= not any(getattr(options, x.dest)
500 for x in commands.option_list)
501 options.enter |= bool(chroot_command)
502
503 if options.enter and options.delete and not options.create:
504 parser.error("Trying to enter the chroot when --delete "
505 "was specified makes no sense.")
506
507 # Finally, discern if we need to create the chroot.
508 chroot_exists = os.path.exists(options.chroot)
509 if options.create or options.enter:
510 # Only create if it's being wiped, or if it doesn't exist.
511 if not options.delete and chroot_exists:
512 options.create = False
513 else:
514 options.download = True
515
516 # Finally, flip create if necessary.
517 if options.enter:
518 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800519
Brian Harringb938c782012-02-29 15:14:38 -0800520 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700521 sdk_version = (bootstrap_latest_version if options.bootstrap
522 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800523 else:
524 sdk_version = options.sdk_version
525
Brian Harring1790ac42012-09-23 08:53:33 -0700526 # Based on selections, fetch the tarball.
527 if options.sdk_url:
528 urls = [options.sdk_url]
529 elif options.bootstrap:
530 urls = GetStage3Urls(sdk_version)
531 else:
532 urls = GetArchStageTarballs(sdk_version)
533
Brian Harringb6cf9142012-09-01 20:43:17 -0700534 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800535 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700536 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700537 with cgroups.SimpleContainChildren('cros_sdk'):
538 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700539
Josh Triplett472a4182013-03-08 11:48:57 -0800540 if options.proxy_sim:
541 _ProxySimSetup(options)
542
David James56e6c2c2012-10-24 23:54:41 -0700543 if options.delete and os.path.exists(options.chroot):
544 lock.write_lock()
545 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800546
David James56e6c2c2012-10-24 23:54:41 -0700547 sdk_cache = os.path.join(options.cache_dir, 'sdks')
548 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700549 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700550
David James56e6c2c2012-10-24 23:54:41 -0700551 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500552 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700553 if not os.path.exists(src):
554 osutils.SafeMakedirs(target)
555 continue
556 lock.write_lock(
557 "Upgrade to %r needed but chroot is locked; please exit "
558 "all instances so this upgrade can finish." % src)
559 if not os.path.exists(src):
560 # Note that while waiting for the write lock, src may've vanished;
561 # it's a rare race during the upgrade process that's a byproduct
562 # of us avoiding taking a write lock to do the src check. If we
563 # took a write lock for that check, it would effectively limit
564 # all cros_sdk for a chroot to a single instance.
565 osutils.SafeMakedirs(target)
566 elif not os.path.exists(target):
567 # Upgrade occurred, but a reversion, or something whacky
568 # occurred writing to the old location. Wipe and continue.
569 os.rename(src, target)
570 else:
571 # Upgrade occurred once already, but either a reversion or
572 # some before/after separate cros_sdk usage is at play.
573 # Wipe and continue.
574 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700575
David James56e6c2c2012-10-24 23:54:41 -0700576 if options.download:
577 lock.write_lock()
578 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700579
David James56e6c2c2012-10-24 23:54:41 -0700580 if options.create:
581 lock.write_lock()
582 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
583 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700584
David James56e6c2c2012-10-24 23:54:41 -0700585 if options.enter:
586 lock.read_lock()
587 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
588 options.chrome_root_mount, chroot_command)