blob: a472bfa839b67355e91fafa9da693da886e0ea12 [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 Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
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
Don Garrett88b8d782014-05-13 17:30:55 -070015from chromite.cbuildbot 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
Ralph Nathan59900422015-03-24 10:41:17 -070019from chromite.lib import cros_logging as logging
Brian Harringb938c782012-02-29 15:14:38 -080020from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080021from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070022from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040023from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080024from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050025from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080026
27cros_build_lib.STRICT_SUDO = True
28
29
Zdenek Behanaa52cea2012-05-30 01:31:11 +020030COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020031
Brian Harringb938c782012-02-29 15:14:38 -080032# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050033MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
34 'src/scripts/sdk_lib/make_chroot.sh')]
35ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
36 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080037
Josh Triplett472a4182013-03-08 11:48:57 -080038# Proxy simulator configuration.
39PROXY_HOST_IP = '192.168.240.1'
40PROXY_PORT = 8080
41PROXY_GUEST_IP = '192.168.240.2'
42PROXY_NETMASK = 30
43PROXY_VETH_PREFIX = 'veth'
44PROXY_CONNECT_PORTS = (80, 443, 9418)
45PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
46PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
47PROXY_APACHE_FALLBACK_PATH = ':'.join(
48 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
49)
50PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
51
Josh Triplett9a495f62013-03-15 18:06:55 -070052# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080053NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080054
Josh Triplett472a4182013-03-08 11:48:57 -080055# Tools needed for --proxy-sim only.
56PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080057
Mike Frysingercc838832014-05-24 13:10:30 -040058
Brian Harring1790ac42012-09-23 08:53:33 -070059def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080060 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070061 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050062 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
63 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070064 for compressor in COMPRESSION_PREFERENCE]
65
66
67def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050068 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070069 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080070
71
Brian Harringae0a5322012-09-15 01:46:51 -070072def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050073 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020074
75 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050076 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020077 urls: List of URLs to try to download. Download will stop on first success.
78
79 Returns:
80 Full path to the downloaded file
81 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020082
Brian Harring1790ac42012-09-23 08:53:33 -070083 # Note we track content length ourselves since certain versions of curl
84 # fail if asked to resume a complete file.
85 # pylint: disable=C0301,W0631
86 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Ralph Nathan7070e6a2015-04-02 10:16:43 -070087 logging.notice('Downloading chroot files.')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020088 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070089 # http://www.logilab.org/ticket/8766
90 # pylint: disable=E1101
91 parsed = urlparse.urlparse(url)
92 tarball_name = os.path.basename(parsed.path)
93 if parsed.scheme in ('', 'file'):
94 if os.path.exists(parsed.path):
95 return parsed.path
96 continue
97 content_length = 0
Ralph Nathan7070e6a2015-04-02 10:16:43 -070098 logging.debug('Attempting download from %s', url)
David Jamesc93e6a4d2014-01-13 11:37:36 -080099 result = retry_util.RunCurl(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500100 ['-I', url], redirect_stdout=True, redirect_stderr=True,
101 print_cmd=False)
Brian Harring1790ac42012-09-23 08:53:33 -0700102 successful = False
103 for header in result.output.splitlines():
104 # We must walk the output to find the string '200 OK' for use cases where
105 # a proxy is involved and may have pushed down the actual header.
106 if header.find('200 OK') != -1:
107 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500108 elif header.lower().startswith('content-length:'):
109 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700110 if successful:
111 break
112 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200113 break
114 else:
115 raise Exception('No valid URLs found!')
116
Brian Harringae0a5322012-09-15 01:46:51 -0700117 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700118 current_size = 0
119 if os.path.exists(tarball_dest):
120 current_size = os.path.getsize(tarball_dest)
121 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700122 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700123 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100124
Brian Harring1790ac42012-09-23 08:53:33 -0700125 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800126 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700127 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
128 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800129
Brian Harring1790ac42012-09-23 08:53:33 -0700130 # Cleanup old tarballs now since we've successfull fetched; only cleanup
131 # the tarballs for our prefix, or unknown ones.
132 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
133 else 'cros-sdk-')
134 for filename in os.listdir(storage_dir):
135 if filename == tarball_name or filename.startswith(ignored_prefix):
136 continue
Brian Harringb938c782012-02-29 15:14:38 -0800137
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700138 logging.info('Cleaning up old tarball: %s' % (filename,))
David James56e6c2c2012-10-24 23:54:41 -0700139 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200140
Brian Harringb938c782012-02-29 15:14:38 -0800141 return tarball_dest
142
143
Brian Harring1790ac42012-09-23 08:53:33 -0700144def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800145 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800146
Brian Harring1790ac42012-09-23 08:53:33 -0700147 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700148 '--chroot', chroot_path,
149 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400150 if nousepkg:
151 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800152
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700153 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800154 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:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700165 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800166 cros_build_lib.RunCommand(cmd, print_cmd=False)
167 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700168 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800169
170
Brian Harringae0a5322012-09-15 01:46:51 -0700171def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700172 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800173 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400174 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
175 # The os.ST_NOSUID constant wasn't added until python-3.2.
176 if st.f_flag & 0x2:
177 cros_build_lib.Die('chroot cannot be in a nosuid mount')
178
Brian Harringae0a5322012-09-15 01:46:51 -0700179 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800180 if chrome_root:
181 cmd.extend(['--chrome_root', chrome_root])
182 if chrome_root_mount:
183 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700184 if workspace:
185 cmd.extend(['--workspace_root', workspace])
186
Brian Harringb938c782012-02-29 15:14:38 -0800187 if len(additional_args) > 0:
188 cmd.append('--')
189 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700190
Ralph Nathan549d3502015-03-26 17:38:42 -0700191 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
192 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700193 # If we were in interactive mode, ignore the exit code; it'll be whatever
194 # they last ran w/in the chroot and won't matter to us one way or another.
195 # Note this does allow chroot entrance to fail and be ignored during
196 # interactive; this is however a rare case and the user will immediately
197 # see it (nor will they be checking the exit code manually).
198 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700199 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800200
201
David James56e6c2c2012-10-24 23:54:41 -0700202def _SudoCommand():
203 """Get the 'sudo' command, along with all needed environment variables."""
204
David James5a73b4d2013-03-07 10:23:40 -0800205 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
206 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700207 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800208 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700209 value = os.environ.get(key)
210 if value is not None:
211 cmd += ['%s=%s' % (key, value)]
212
213 # Pass in the path to the depot_tools so that users can access them from
214 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400215 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500216
David James56e6c2c2012-10-24 23:54:41 -0700217 return cmd
218
219
Josh Triplett472a4182013-03-08 11:48:57 -0800220def _ReportMissing(missing):
221 """Report missing utilities, then exit.
222
223 Args:
224 missing: List of missing utilities, as returned by
225 osutils.FindMissingBinaries. If non-empty, will not return.
226 """
227
228 if missing:
229 raise SystemExit(
230 'The tool(s) %s were not found.\n'
231 'Please install the appropriate package in your host.\n'
232 'Example(ubuntu):\n'
233 ' sudo apt-get install <packagename>'
234 % ', '.join(missing))
235
236
237def _ProxySimSetup(options):
238 """Set up proxy simulator, and return only in the child environment.
239
240 TODO: Ideally, this should support multiple concurrent invocations of
241 cros_sdk --proxy-sim; currently, such invocations will conflict with each
242 other due to the veth device names and IP addresses. Either this code would
243 need to generate fresh, unused names for all of these before forking, or it
244 would need to support multiple concurrent cros_sdk invocations sharing one
245 proxy and allowing it to exit when unused (without counting on any local
246 service-management infrastructure on the host).
247 """
248
249 may_need_mpm = False
250 apache_bin = osutils.Which('apache2')
251 if apache_bin is None:
252 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
253 if apache_bin is None:
254 _ReportMissing(('apache2',))
255 else:
256 may_need_mpm = True
257
258 # Module names and .so names included for ease of grepping.
259 apache_modules = [('proxy_module', 'mod_proxy.so'),
260 ('proxy_connect_module', 'mod_proxy_connect.so'),
261 ('proxy_http_module', 'mod_proxy_http.so'),
262 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
263
264 # Find the apache module directory, and make sure it has the modules we need.
265 module_dirs = {}
266 for g in PROXY_APACHE_MODULE_GLOBS:
267 for mod, so in apache_modules:
268 for f in glob.glob(os.path.join(g, so)):
269 module_dirs.setdefault(os.path.dirname(f), []).append(so)
270 for apache_module_path, modules_found in module_dirs.iteritems():
271 if len(modules_found) == len(apache_modules):
272 break
273 else:
274 # Appease cros lint, which doesn't understand that this else block will not
275 # fall through to the subsequent code which relies on apache_module_path.
276 apache_module_path = None
277 raise SystemExit(
278 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500279 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800280
281 def check_add_module(name):
282 so = 'mod_%s.so' % name
283 if os.access(os.path.join(apache_module_path, so), os.F_OK):
284 mod = '%s_module' % name
285 apache_modules.append((mod, so))
286 return True
287 return False
288
289 check_add_module('authz_core')
290 if may_need_mpm:
291 for mpm in PROXY_APACHE_MPMS:
292 if check_add_module('mpm_%s' % mpm):
293 break
294
295 veth_host = '%s-host' % PROXY_VETH_PREFIX
296 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
297
298 # Set up pipes from parent to child and vice versa.
299 # The child writes a byte to the parent after calling unshare, so that the
300 # parent can then assign the guest end of the veth interface to the child's
301 # new network namespace. The parent then writes a byte to the child after
302 # assigning the guest interface, so that the child can then configure that
303 # interface. In both cases, if we get back an EOF when reading from the
304 # pipe, we assume the other end exited with an error message, so just exit.
305 parent_readfd, child_writefd = os.pipe()
306 child_readfd, parent_writefd = os.pipe()
307 SUCCESS_FLAG = '+'
308
309 pid = os.fork()
310 if not pid:
311 os.close(parent_readfd)
312 os.close(parent_writefd)
313
314 namespaces.Unshare(namespaces.CLONE_NEWNET)
315 os.write(child_writefd, SUCCESS_FLAG)
316 os.close(child_writefd)
317 if os.read(child_readfd, 1) != SUCCESS_FLAG:
318 # Parent failed; it will have already have outputted an error message.
319 sys.exit(1)
320 os.close(child_readfd)
321
322 # Set up child side of the network.
323 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500324 ('ip', 'link', 'set', 'up', 'lo'),
325 ('ip', 'address', 'add',
326 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
327 'dev', veth_guest),
328 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800329 )
330 try:
331 for cmd in commands:
332 cros_build_lib.RunCommand(cmd, print_cmd=False)
333 except cros_build_lib.RunCommandError:
334 raise SystemExit('Running %r failed!' % (cmd,))
335
336 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
337 for proto in ('http', 'https', 'ftp'):
338 os.environ[proto + '_proxy'] = proxy_url
339 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
340 os.environ.pop(v, None)
341 return
342
343 os.close(child_readfd)
344 os.close(child_writefd)
345
346 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
347 # Child failed; it will have already have outputted an error message.
348 sys.exit(1)
349 os.close(parent_readfd)
350
351 # Set up parent side of the network.
352 uid = int(os.environ.get('SUDO_UID', '0'))
353 gid = int(os.environ.get('SUDO_GID', '0'))
354 if uid == 0 or gid == 0:
355 for username in PROXY_APACHE_FALLBACK_USERS:
356 try:
357 pwnam = pwd.getpwnam(username)
358 uid, gid = pwnam.pw_uid, pwnam.pw_gid
359 break
360 except KeyError:
361 continue
362 if uid == 0 or gid == 0:
363 raise SystemExit('Could not find a non-root user to run Apache as')
364
365 chroot_parent, chroot_base = os.path.split(options.chroot)
366 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
367 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
368
369 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500370 'User #%u' % uid,
371 'Group #%u' % gid,
372 'PidFile %s' % pid_file,
373 'ErrorLog %s' % log_file,
374 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
375 'ServerName %s' % PROXY_HOST_IP,
376 'ProxyRequests On',
377 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800378 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500379 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
380 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800381 ]
382 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500383 ('ip', 'link', 'add', 'name', veth_host,
384 'type', 'veth', 'peer', 'name', veth_guest),
385 ('ip', 'address', 'add',
386 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
387 'dev', veth_host),
388 ('ip', 'link', 'set', veth_host, 'up'),
389 ([apache_bin, '-f', '/dev/null'] +
390 [arg for d in apache_directives for arg in ('-C', d)]),
391 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800392 )
393 cmd = None # Make cros lint happy.
394 try:
395 for cmd in commands:
396 cros_build_lib.RunCommand(cmd, print_cmd=False)
397 except cros_build_lib.RunCommandError:
398 # Clean up existing interfaces, if any.
399 cmd_cleanup = ('ip', 'link', 'del', veth_host)
400 try:
401 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
402 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700403 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800404 raise SystemExit('Running %r failed!' % (cmd,))
405 os.write(parent_writefd, SUCCESS_FLAG)
406 os.close(parent_writefd)
407
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400408 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800409
410
Mike Frysingera78a56e2012-11-20 06:02:30 -0500411def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700412 """Re-execute cros_sdk as root.
413
414 Also unshare the mount namespace so as to ensure that processes outside
415 the chroot can't mess with our mounts.
416 """
417 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500418 cmd = _SudoCommand() + ['--'] + argv
419 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500420 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400421 # We must set up the cgroups mounts before we enter our own namespace.
422 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800423 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500424 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700425
426
Mike Frysinger34db8692013-11-11 14:54:08 -0500427def _CreateParser(sdk_latest_version, bootstrap_latest_version):
428 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700429 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800430
Brian Harring218e13c2012-10-10 16:21:26 -0700431This script is used for manipulating local chroot environments; creating,
432deleting, downloading, etc. If given --enter (or no args), it defaults
433to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800434
Brian Harring218e13c2012-10-10 16:21:26 -0700435If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700436
Brian Harring218e13c2012-10-10 16:21:26 -0700437 parser = commandline.OptionParser(usage=usage, caching=True)
438
Mike Frysinger34db8692013-11-11 14:54:08 -0500439 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500440 default_chroot = os.path.join(constants.SOURCE_ROOT,
441 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700442 parser.add_option(
443 '--chroot', dest='chroot', default=default_chroot, type='path',
444 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800445
Brian Harring218e13c2012-10-10 16:21:26 -0700446 parser.add_option('--chrome_root', default=None, type='path',
447 help='Mount this chrome root into the SDK chroot')
448 parser.add_option('--chrome_root_mount', default=None, type='path',
449 help='Mount chrome into this path inside SDK chroot')
450 parser.add_option('--nousepkg', action='store_true', default=False,
451 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800452 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700453 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800454 help=('''Use sdk tarball located at this url.
455 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700456 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500457 help=('Use this sdk version. For prebuilt, current is %r'
458 ', for bootstrapping it is %r.'
459 % (sdk_latest_version, bootstrap_latest_version)))
Don Garrett230d1b22015-03-09 16:21:19 -0700460 parser.add_option('--workspace', default=None,
461 help='Workspace directory to mount into the chroot.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500462
463 # Commands.
464 group = parser.add_option_group('Commands')
465 group.add_option(
466 '--enter', action='store_true', default=False,
467 help='Enter the SDK chroot. Implies --create.')
468 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500469 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500470 help='Create the chroot only if it does not already exist. '
471 'Implies --download.')
472 group.add_option(
473 '--bootstrap', action='store_true', default=False,
474 help='Build everything from scratch, including the sdk. '
475 'Use this only if you need to validate a change '
476 'that affects SDK creation itself (toolchain and '
477 'build are typically the only folk who need this). '
478 'Note this will quite heavily slow down the build. '
479 'This option implies --create --nousepkg.')
480 group.add_option(
481 '-r', '--replace', action='store_true', default=False,
482 help='Replace an existing SDK chroot. Basically an alias '
483 'for --delete --create.')
484 group.add_option(
485 '--delete', action='store_true', default=False,
486 help='Delete the current SDK chroot if it exists.')
487 group.add_option(
488 '--download', action='store_true', default=False,
489 help='Download the sdk.')
490 commands = group
491
Mike Frysinger80dfce92014-04-21 10:58:53 -0400492 # Namespace options.
493 group = parser.add_option_group('Namespaces')
494 group.add_option('--proxy-sim', action='store_true', default=False,
495 help='Simulate a restrictive network requiring an outbound'
496 ' proxy.')
497 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger41f28032014-09-29 16:53:41 -0500498 default=True, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400499 help='Do not create a new PID namespace.')
500
Mike Frysinger34db8692013-11-11 14:54:08 -0500501 # Internal options.
502 group = parser.add_option_group(
503 'Internal Chromium OS Build Team Options',
504 'Caution: these are for meant for the Chromium OS build team only')
505 group.add_option('--buildbot-log-version', default=False, action='store_true',
506 help='Log SDK version for buildbot consumption')
507
508 return (parser, commands)
509
510
511def main(argv):
512 conf = cros_build_lib.LoadKeyValueFile(
513 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
514 ignore_missing=True)
515 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
516 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
517 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700518 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800519
520 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500521 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800522
Brian Harring1790ac42012-09-23 08:53:33 -0700523 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700524 if host != 'x86_64':
525 parser.error(
526 "cros_sdk is currently only supported on x86_64; you're running"
527 " %s. Please find a x86_64 machine." % (host,))
528
Josh Triplett472a4182013-03-08 11:48:57 -0800529 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
530 if options.proxy_sim:
531 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800532
David James471532c2013-01-21 10:23:31 -0800533 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400534 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400535 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400536 else:
537 first_pid = None
David James471532c2013-01-21 10:23:31 -0800538
Brian Harring218e13c2012-10-10 16:21:26 -0700539 # Expand out the aliases...
540 if options.replace:
541 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800542
Brian Harring218e13c2012-10-10 16:21:26 -0700543 if options.bootstrap:
544 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800545
Brian Harring218e13c2012-10-10 16:21:26 -0700546 # If a command is not given, default to enter.
547 options.enter |= not any(getattr(options, x.dest)
548 for x in commands.option_list)
549 options.enter |= bool(chroot_command)
550
551 if options.enter and options.delete and not options.create:
552 parser.error("Trying to enter the chroot when --delete "
553 "was specified makes no sense.")
554
555 # Finally, discern if we need to create the chroot.
556 chroot_exists = os.path.exists(options.chroot)
557 if options.create or options.enter:
558 # Only create if it's being wiped, or if it doesn't exist.
559 if not options.delete and chroot_exists:
560 options.create = False
561 else:
562 options.download = True
563
564 # Finally, flip create if necessary.
565 if options.enter:
566 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800567
Brian Harringb938c782012-02-29 15:14:38 -0800568 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700569 sdk_version = (bootstrap_latest_version if options.bootstrap
570 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800571 else:
572 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500573 if options.buildbot_log_version:
574 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800575
Brian Harring1790ac42012-09-23 08:53:33 -0700576 # Based on selections, fetch the tarball.
577 if options.sdk_url:
578 urls = [options.sdk_url]
579 elif options.bootstrap:
580 urls = GetStage3Urls(sdk_version)
581 else:
582 urls = GetArchStageTarballs(sdk_version)
583
Brian Harringb6cf9142012-09-01 20:43:17 -0700584 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700585 lock_path = os.path.join(
586 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400587 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700588 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700589
Josh Triplett472a4182013-03-08 11:48:57 -0800590 if options.proxy_sim:
591 _ProxySimSetup(options)
592
David James56e6c2c2012-10-24 23:54:41 -0700593 if options.delete and os.path.exists(options.chroot):
594 lock.write_lock()
595 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800596
David James56e6c2c2012-10-24 23:54:41 -0700597 sdk_cache = os.path.join(options.cache_dir, 'sdks')
598 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700599 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700600
David James56e6c2c2012-10-24 23:54:41 -0700601 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500602 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700603 if not os.path.exists(src):
604 osutils.SafeMakedirs(target)
605 continue
606 lock.write_lock(
607 "Upgrade to %r needed but chroot is locked; please exit "
608 "all instances so this upgrade can finish." % src)
609 if not os.path.exists(src):
610 # Note that while waiting for the write lock, src may've vanished;
611 # it's a rare race during the upgrade process that's a byproduct
612 # of us avoiding taking a write lock to do the src check. If we
613 # took a write lock for that check, it would effectively limit
614 # all cros_sdk for a chroot to a single instance.
615 osutils.SafeMakedirs(target)
616 elif not os.path.exists(target):
617 # Upgrade occurred, but a reversion, or something whacky
618 # occurred writing to the old location. Wipe and continue.
619 os.rename(src, target)
620 else:
621 # Upgrade occurred once already, but either a reversion or
622 # some before/after separate cros_sdk usage is at play.
623 # Wipe and continue.
624 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700625
David James56e6c2c2012-10-24 23:54:41 -0700626 if options.download:
627 lock.write_lock()
628 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700629
David James56e6c2c2012-10-24 23:54:41 -0700630 if options.create:
631 lock.write_lock()
632 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
633 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700634
David James56e6c2c2012-10-24 23:54:41 -0700635 if options.enter:
636 lock.read_lock()
637 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700638 options.chrome_root_mount, options.workspace,
639 chroot_command)