blob: 3d36cfd3088f65f53c38c3ff214302bfbfb7d308 [file] [log] [blame]
Mike Frysinger2de7f042012-07-10 04:45:03 -04001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysinger80dfce92014-04-21 10:58:53 -04005"""This script fetches and prepares an SDK chroot."""
Brian Harringb938c782012-02-29 15:14:38 -08006
Mike Frysinger1d4752b2014-11-08 04:00:18 -05007# pylint: disable=bad-continuation
8# pylint: disable=bad-whitespace
9
Mike Frysinger383367e2014-09-16 15:06:17 -040010from __future__ import print_function
11
Josh Triplett472a4182013-03-08 11:48:57 -080012import glob
Brian Harringb938c782012-02-29 15:14:38 -080013import os
Josh Triplett472a4182013-03-08 11:48:57 -080014import pwd
David James56e6c2c2012-10-24 23:54:41 -070015import sys
Brian Harringb938c782012-02-29 15:14:38 -080016import urlparse
17
Don Garrett88b8d782014-05-13 17:30:55 -070018from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080019from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070020from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080021from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080022from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080023from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070024from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040025from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080026from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050027from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080028
29cros_build_lib.STRICT_SUDO = True
30
31
Zdenek Behanaa52cea2012-05-30 01:31:11 +020032COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020033
Brian Harringb938c782012-02-29 15:14:38 -080034# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050035MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
36 'src/scripts/sdk_lib/make_chroot.sh')]
37ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
38 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080039
Josh Triplett472a4182013-03-08 11:48:57 -080040# Proxy simulator configuration.
41PROXY_HOST_IP = '192.168.240.1'
42PROXY_PORT = 8080
43PROXY_GUEST_IP = '192.168.240.2'
44PROXY_NETMASK = 30
45PROXY_VETH_PREFIX = 'veth'
46PROXY_CONNECT_PORTS = (80, 443, 9418)
47PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
48PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
49PROXY_APACHE_FALLBACK_PATH = ':'.join(
50 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
51)
52PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
53
Josh Triplett9a495f62013-03-15 18:06:55 -070054# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080055NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080056
Josh Triplett472a4182013-03-08 11:48:57 -080057# Tools needed for --proxy-sim only.
58PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080059
Mike Frysingercc838832014-05-24 13:10:30 -040060
Brian Harring1790ac42012-09-23 08:53:33 -070061def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080062 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070063 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050064 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
65 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070066 for compressor in COMPRESSION_PREFERENCE]
67
68
69def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050070 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070071 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080072
73
Brian Harringae0a5322012-09-15 01:46:51 -070074def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050075 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020076
77 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050078 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020079 urls: List of URLs to try to download. Download will stop on first success.
80
81 Returns:
82 Full path to the downloaded file
83 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020084
Brian Harring1790ac42012-09-23 08:53:33 -070085 # Note we track content length ourselves since certain versions of curl
86 # fail if asked to resume a complete file.
87 # pylint: disable=C0301,W0631
88 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020089 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070090 # http://www.logilab.org/ticket/8766
91 # pylint: disable=E1101
92 parsed = urlparse.urlparse(url)
93 tarball_name = os.path.basename(parsed.path)
94 if parsed.scheme in ('', 'file'):
95 if os.path.exists(parsed.path):
96 return parsed.path
97 continue
98 content_length = 0
Mike Frysinger383367e2014-09-16 15:06:17 -040099 print('Attempting download: %s' % url)
David Jamesc93e6a4d2014-01-13 11:37:36 -0800100 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700101 ['-I', url], redirect_stdout=True, redirect_stderr=True,
102 print_cmd=False)
103 successful = False
104 for header in result.output.splitlines():
105 # We must walk the output to find the string '200 OK' for use cases where
106 # a proxy is involved and may have pushed down the actual header.
107 if header.find('200 OK') != -1:
108 successful = True
109 elif header.lower().startswith("content-length:"):
110 content_length = int(header.split(":", 1)[-1].strip())
111 if successful:
112 break
113 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200114 break
115 else:
116 raise Exception('No valid URLs found!')
117
Brian Harringae0a5322012-09-15 01:46:51 -0700118 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700119 current_size = 0
120 if os.path.exists(tarball_dest):
121 current_size = os.path.getsize(tarball_dest)
122 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700123 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700124 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100125
Brian Harring1790ac42012-09-23 08:53:33 -0700126 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800127 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700128 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
129 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800130
Brian Harring1790ac42012-09-23 08:53:33 -0700131 # Cleanup old tarballs now since we've successfull fetched; only cleanup
132 # the tarballs for our prefix, or unknown ones.
133 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
134 else 'cros-sdk-')
135 for filename in os.listdir(storage_dir):
136 if filename == tarball_name or filename.startswith(ignored_prefix):
137 continue
Brian Harringb938c782012-02-29 15:14:38 -0800138
Mike Frysinger383367e2014-09-16 15:06:17 -0400139 print('Cleaning up old tarball: %s' % (filename,))
David James56e6c2c2012-10-24 23:54:41 -0700140 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200141
Brian Harringb938c782012-02-29 15:14:38 -0800142 return tarball_dest
143
144
Brian Harring1790ac42012-09-23 08:53:33 -0700145def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800146 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800147
Brian Harring1790ac42012-09-23 08:53:33 -0700148 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700149 '--chroot', chroot_path,
150 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400151 if nousepkg:
152 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800153
154 try:
155 cros_build_lib.RunCommand(cmd, print_cmd=False)
156 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700157 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800158
159
160def DeleteChroot(chroot_path):
161 """Deletes an existing chroot"""
162 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
163 '--delete']
164 try:
165 cros_build_lib.RunCommand(cmd, print_cmd=False)
166 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700167 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800168
169
Brian Harringae0a5322012-09-15 01:46:51 -0700170def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
171 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800172 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400173 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
174 # The os.ST_NOSUID constant wasn't added until python-3.2.
175 if st.f_flag & 0x2:
176 cros_build_lib.Die('chroot cannot be in a nosuid mount')
177
Brian Harringae0a5322012-09-15 01:46:51 -0700178 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800179 if chrome_root:
180 cmd.extend(['--chrome_root', chrome_root])
181 if chrome_root_mount:
182 cmd.extend(['--chrome_root_mount', chrome_root_mount])
183 if len(additional_args) > 0:
184 cmd.append('--')
185 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700186
187 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
188 # If we were in interactive mode, ignore the exit code; it'll be whatever
189 # they last ran w/in the chroot and won't matter to us one way or another.
190 # Note this does allow chroot entrance to fail and be ignored during
191 # interactive; this is however a rare case and the user will immediately
192 # see it (nor will they be checking the exit code manually).
193 if ret.returncode != 0 and additional_args:
194 raise SystemExit('Running %r failed with exit code %i'
195 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800196
197
David James56e6c2c2012-10-24 23:54:41 -0700198def _SudoCommand():
199 """Get the 'sudo' command, along with all needed environment variables."""
200
David James5a73b4d2013-03-07 10:23:40 -0800201 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
202 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700203 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800204 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700205 value = os.environ.get(key)
206 if value is not None:
207 cmd += ['%s=%s' % (key, value)]
208
209 # Pass in the path to the depot_tools so that users can access them from
210 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400211 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500212
David James56e6c2c2012-10-24 23:54:41 -0700213 return cmd
214
215
Josh Triplett472a4182013-03-08 11:48:57 -0800216def _ReportMissing(missing):
217 """Report missing utilities, then exit.
218
219 Args:
220 missing: List of missing utilities, as returned by
221 osutils.FindMissingBinaries. If non-empty, will not return.
222 """
223
224 if missing:
225 raise SystemExit(
226 'The tool(s) %s were not found.\n'
227 'Please install the appropriate package in your host.\n'
228 'Example(ubuntu):\n'
229 ' sudo apt-get install <packagename>'
230 % ', '.join(missing))
231
232
233def _ProxySimSetup(options):
234 """Set up proxy simulator, and return only in the child environment.
235
236 TODO: Ideally, this should support multiple concurrent invocations of
237 cros_sdk --proxy-sim; currently, such invocations will conflict with each
238 other due to the veth device names and IP addresses. Either this code would
239 need to generate fresh, unused names for all of these before forking, or it
240 would need to support multiple concurrent cros_sdk invocations sharing one
241 proxy and allowing it to exit when unused (without counting on any local
242 service-management infrastructure on the host).
243 """
244
245 may_need_mpm = False
246 apache_bin = osutils.Which('apache2')
247 if apache_bin is None:
248 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
249 if apache_bin is None:
250 _ReportMissing(('apache2',))
251 else:
252 may_need_mpm = True
253
254 # Module names and .so names included for ease of grepping.
255 apache_modules = [('proxy_module', 'mod_proxy.so'),
256 ('proxy_connect_module', 'mod_proxy_connect.so'),
257 ('proxy_http_module', 'mod_proxy_http.so'),
258 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
259
260 # Find the apache module directory, and make sure it has the modules we need.
261 module_dirs = {}
262 for g in PROXY_APACHE_MODULE_GLOBS:
263 for mod, so in apache_modules:
264 for f in glob.glob(os.path.join(g, so)):
265 module_dirs.setdefault(os.path.dirname(f), []).append(so)
266 for apache_module_path, modules_found in module_dirs.iteritems():
267 if len(modules_found) == len(apache_modules):
268 break
269 else:
270 # Appease cros lint, which doesn't understand that this else block will not
271 # fall through to the subsequent code which relies on apache_module_path.
272 apache_module_path = None
273 raise SystemExit(
274 'Could not find apache module path containing all required modules: %s'
275 % ', '.join(so for mod, so in apache_modules))
276
277 def check_add_module(name):
278 so = 'mod_%s.so' % name
279 if os.access(os.path.join(apache_module_path, so), os.F_OK):
280 mod = '%s_module' % name
281 apache_modules.append((mod, so))
282 return True
283 return False
284
285 check_add_module('authz_core')
286 if may_need_mpm:
287 for mpm in PROXY_APACHE_MPMS:
288 if check_add_module('mpm_%s' % mpm):
289 break
290
291 veth_host = '%s-host' % PROXY_VETH_PREFIX
292 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
293
294 # Set up pipes from parent to child and vice versa.
295 # The child writes a byte to the parent after calling unshare, so that the
296 # parent can then assign the guest end of the veth interface to the child's
297 # new network namespace. The parent then writes a byte to the child after
298 # assigning the guest interface, so that the child can then configure that
299 # interface. In both cases, if we get back an EOF when reading from the
300 # pipe, we assume the other end exited with an error message, so just exit.
301 parent_readfd, child_writefd = os.pipe()
302 child_readfd, parent_writefd = os.pipe()
303 SUCCESS_FLAG = '+'
304
305 pid = os.fork()
306 if not pid:
307 os.close(parent_readfd)
308 os.close(parent_writefd)
309
310 namespaces.Unshare(namespaces.CLONE_NEWNET)
311 os.write(child_writefd, SUCCESS_FLAG)
312 os.close(child_writefd)
313 if os.read(child_readfd, 1) != SUCCESS_FLAG:
314 # Parent failed; it will have already have outputted an error message.
315 sys.exit(1)
316 os.close(child_readfd)
317
318 # Set up child side of the network.
319 commands = (
Mike Frysinger7e82d9a2014-10-09 23:14:55 -0400320 ('ip', 'link', 'set', 'up', 'lo'),
Josh Triplett472a4182013-03-08 11:48:57 -0800321 ('ip', 'address', 'add',
322 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
323 'dev', veth_guest),
324 ('ip', 'link', 'set', veth_guest, 'up'),
325 )
326 try:
327 for cmd in commands:
328 cros_build_lib.RunCommand(cmd, print_cmd=False)
329 except cros_build_lib.RunCommandError:
330 raise SystemExit('Running %r failed!' % (cmd,))
331
332 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
333 for proto in ('http', 'https', 'ftp'):
334 os.environ[proto + '_proxy'] = proxy_url
335 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
336 os.environ.pop(v, None)
337 return
338
339 os.close(child_readfd)
340 os.close(child_writefd)
341
342 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
343 # Child failed; it will have already have outputted an error message.
344 sys.exit(1)
345 os.close(parent_readfd)
346
347 # Set up parent side of the network.
348 uid = int(os.environ.get('SUDO_UID', '0'))
349 gid = int(os.environ.get('SUDO_GID', '0'))
350 if uid == 0 or gid == 0:
351 for username in PROXY_APACHE_FALLBACK_USERS:
352 try:
353 pwnam = pwd.getpwnam(username)
354 uid, gid = pwnam.pw_uid, pwnam.pw_gid
355 break
356 except KeyError:
357 continue
358 if uid == 0 or gid == 0:
359 raise SystemExit('Could not find a non-root user to run Apache as')
360
361 chroot_parent, chroot_base = os.path.split(options.chroot)
362 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
363 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
364
365 apache_directives = [
366 'User #%u' % uid,
367 'Group #%u' % gid,
368 'PidFile %s' % pid_file,
369 'ErrorLog %s' % log_file,
370 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
371 'ServerName %s' % PROXY_HOST_IP,
372 'ProxyRequests On',
373 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
374 ] + [
375 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
376 for (mod, so) in apache_modules
377 ]
378 commands = (
379 ('ip', 'link', 'add', 'name', veth_host,
380 'type', 'veth', 'peer', 'name', veth_guest),
381 ('ip', 'address', 'add',
382 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
383 'dev', veth_host),
384 ('ip', 'link', 'set', veth_host, 'up'),
385 [apache_bin, '-f', '/dev/null']
386 + [arg for d in apache_directives for arg in ('-C', d)],
387 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
388 )
389 cmd = None # Make cros lint happy.
390 try:
391 for cmd in commands:
392 cros_build_lib.RunCommand(cmd, print_cmd=False)
393 except cros_build_lib.RunCommandError:
394 # Clean up existing interfaces, if any.
395 cmd_cleanup = ('ip', 'link', 'del', veth_host)
396 try:
397 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
398 except cros_build_lib.RunCommandError:
399 cros_build_lib.Error('running %r failed', cmd_cleanup)
400 raise SystemExit('Running %r failed!' % (cmd,))
401 os.write(parent_writefd, SUCCESS_FLAG)
402 os.close(parent_writefd)
403
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400404 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800405
406
Mike Frysingera78a56e2012-11-20 06:02:30 -0500407def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700408 """Re-execute cros_sdk as root.
409
410 Also unshare the mount namespace so as to ensure that processes outside
411 the chroot can't mess with our mounts.
412 """
413 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500414 cmd = _SudoCommand() + ['--'] + argv
415 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500416 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400417 # We must set up the cgroups mounts before we enter our own namespace.
418 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800419 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400420 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700421
422
Mike Frysinger34db8692013-11-11 14:54:08 -0500423def _CreateParser(sdk_latest_version, bootstrap_latest_version):
424 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700425 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800426
Brian Harring218e13c2012-10-10 16:21:26 -0700427This script is used for manipulating local chroot environments; creating,
428deleting, downloading, etc. If given --enter (or no args), it defaults
429to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800430
Brian Harring218e13c2012-10-10 16:21:26 -0700431If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700432
Brian Harring218e13c2012-10-10 16:21:26 -0700433 parser = commandline.OptionParser(usage=usage, caching=True)
434
Mike Frysinger34db8692013-11-11 14:54:08 -0500435 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500436 default_chroot = os.path.join(constants.SOURCE_ROOT,
437 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700438 parser.add_option(
439 '--chroot', dest='chroot', default=default_chroot, type='path',
440 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800441
Brian Harring218e13c2012-10-10 16:21:26 -0700442 parser.add_option('--chrome_root', default=None, type='path',
443 help='Mount this chrome root into the SDK chroot')
444 parser.add_option('--chrome_root_mount', default=None, type='path',
445 help='Mount chrome into this path inside SDK chroot')
446 parser.add_option('--nousepkg', action='store_true', default=False,
447 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800448 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700449 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800450 help=('''Use sdk tarball located at this url.
451 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700452 parser.add_option('--sdk-version', default=None,
453 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500454 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700455 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500456
457 # Commands.
458 group = parser.add_option_group('Commands')
459 group.add_option(
460 '--enter', action='store_true', default=False,
461 help='Enter the SDK chroot. Implies --create.')
462 group.add_option(
463 '--create', action='store_true',default=False,
464 help='Create the chroot only if it does not already exist. '
465 'Implies --download.')
466 group.add_option(
467 '--bootstrap', action='store_true', default=False,
468 help='Build everything from scratch, including the sdk. '
469 'Use this only if you need to validate a change '
470 'that affects SDK creation itself (toolchain and '
471 'build are typically the only folk who need this). '
472 'Note this will quite heavily slow down the build. '
473 'This option implies --create --nousepkg.')
474 group.add_option(
475 '-r', '--replace', action='store_true', default=False,
476 help='Replace an existing SDK chroot. Basically an alias '
477 'for --delete --create.')
478 group.add_option(
479 '--delete', action='store_true', default=False,
480 help='Delete the current SDK chroot if it exists.')
481 group.add_option(
482 '--download', action='store_true', default=False,
483 help='Download the sdk.')
484 commands = group
485
Mike Frysinger80dfce92014-04-21 10:58:53 -0400486 # Namespace options.
487 group = parser.add_option_group('Namespaces')
488 group.add_option('--proxy-sim', action='store_true', default=False,
489 help='Simulate a restrictive network requiring an outbound'
490 ' proxy.')
Mike Frysinger94b32a12014-09-12 14:28:13 -0400491 # TODO(vapier): Turn off pid ns until ccache issues can be fixed.
492 # http://crbug.com/411984
Mike Frysinger80dfce92014-04-21 10:58:53 -0400493 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger94b32a12014-09-12 14:28:13 -0400494 default=False, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400495 help='Do not create a new PID namespace.')
496
Mike Frysinger34db8692013-11-11 14:54:08 -0500497 # Internal options.
498 group = parser.add_option_group(
499 'Internal Chromium OS Build Team Options',
500 'Caution: these are for meant for the Chromium OS build team only')
501 group.add_option('--buildbot-log-version', default=False, action='store_true',
502 help='Log SDK version for buildbot consumption')
503
504 return (parser, commands)
505
506
507def main(argv):
508 conf = cros_build_lib.LoadKeyValueFile(
509 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
510 ignore_missing=True)
511 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
512 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
513 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700514 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800515
516 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500517 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800518
Brian Harring1790ac42012-09-23 08:53:33 -0700519 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700520 if host != 'x86_64':
521 parser.error(
522 "cros_sdk is currently only supported on x86_64; you're running"
523 " %s. Please find a x86_64 machine." % (host,))
524
Josh Triplett472a4182013-03-08 11:48:57 -0800525 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
526 if options.proxy_sim:
527 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800528
David James471532c2013-01-21 10:23:31 -0800529 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400530 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400531 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400532 else:
533 first_pid = None
David James471532c2013-01-21 10:23:31 -0800534
Brian Harring218e13c2012-10-10 16:21:26 -0700535 # Expand out the aliases...
536 if options.replace:
537 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800538
Brian Harring218e13c2012-10-10 16:21:26 -0700539 if options.bootstrap:
540 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800541
Brian Harring218e13c2012-10-10 16:21:26 -0700542 # If a command is not given, default to enter.
543 options.enter |= not any(getattr(options, x.dest)
544 for x in commands.option_list)
545 options.enter |= bool(chroot_command)
546
547 if options.enter and options.delete and not options.create:
548 parser.error("Trying to enter the chroot when --delete "
549 "was specified makes no sense.")
550
551 # Finally, discern if we need to create the chroot.
552 chroot_exists = os.path.exists(options.chroot)
553 if options.create or options.enter:
554 # Only create if it's being wiped, or if it doesn't exist.
555 if not options.delete and chroot_exists:
556 options.create = False
557 else:
558 options.download = True
559
560 # Finally, flip create if necessary.
561 if options.enter:
562 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800563
Brian Harringb938c782012-02-29 15:14:38 -0800564 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700565 sdk_version = (bootstrap_latest_version if options.bootstrap
566 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800567 else:
568 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500569 if options.buildbot_log_version:
570 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800571
Brian Harring1790ac42012-09-23 08:53:33 -0700572 # Based on selections, fetch the tarball.
573 if options.sdk_url:
574 urls = [options.sdk_url]
575 elif options.bootstrap:
576 urls = GetStage3Urls(sdk_version)
577 else:
578 urls = GetArchStageTarballs(sdk_version)
579
Brian Harringb6cf9142012-09-01 20:43:17 -0700580 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800581 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700582 '.%s_lock' % os.path.basename(options.chroot))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400583 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700584 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700585
Josh Triplett472a4182013-03-08 11:48:57 -0800586 if options.proxy_sim:
587 _ProxySimSetup(options)
588
David James56e6c2c2012-10-24 23:54:41 -0700589 if options.delete and os.path.exists(options.chroot):
590 lock.write_lock()
591 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800592
David James56e6c2c2012-10-24 23:54:41 -0700593 sdk_cache = os.path.join(options.cache_dir, 'sdks')
594 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700595 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700596
David James56e6c2c2012-10-24 23:54:41 -0700597 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500598 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700599 if not os.path.exists(src):
600 osutils.SafeMakedirs(target)
601 continue
602 lock.write_lock(
603 "Upgrade to %r needed but chroot is locked; please exit "
604 "all instances so this upgrade can finish." % src)
605 if not os.path.exists(src):
606 # Note that while waiting for the write lock, src may've vanished;
607 # it's a rare race during the upgrade process that's a byproduct
608 # of us avoiding taking a write lock to do the src check. If we
609 # took a write lock for that check, it would effectively limit
610 # all cros_sdk for a chroot to a single instance.
611 osutils.SafeMakedirs(target)
612 elif not os.path.exists(target):
613 # Upgrade occurred, but a reversion, or something whacky
614 # occurred writing to the old location. Wipe and continue.
615 os.rename(src, target)
616 else:
617 # Upgrade occurred once already, but either a reversion or
618 # some before/after separate cros_sdk usage is at play.
619 # Wipe and continue.
620 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700621
David James56e6c2c2012-10-24 23:54:41 -0700622 if options.download:
623 lock.write_lock()
624 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700625
David James56e6c2c2012-10-24 23:54:41 -0700626 if options.create:
627 lock.write_lock()
628 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
629 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700630
David James56e6c2c2012-10-24 23:54:41 -0700631 if options.enter:
632 lock.read_lock()
633 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
634 options.chrome_root_mount, chroot_command)