blob: 106dff82fc15114d4b3d9c043764eecda66e9c44 [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 Frysingerbf6b36c2015-04-17 15:45:45 -0400100 ['-I', url], fail=False, redirect_stdout=True, redirect_stderr=True,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500101 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(
Mike Frysingerbf6b36c2015-04-17 15:45:45 -0400127 ['-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
Brian Harring1790ac42012-09-23 08:53:33 -0700128 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
David Purselld99f6222015-05-04 16:52:59 -0700144def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False,
145 workspace=None):
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
David Purselld99f6222015-05-04 16:52:59 -0700154 if workspace:
155 cmd.extend(['--workspace_root', workspace])
156
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700157 logging.notice('Creating chroot. This may take a few minutes...')
Brian Harringb938c782012-02-29 15:14:38 -0800158 try:
159 cros_build_lib.RunCommand(cmd, print_cmd=False)
160 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700161 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800162
163
164def DeleteChroot(chroot_path):
165 """Deletes an existing chroot"""
166 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
167 '--delete']
168 try:
Ralph Nathan7070e6a2015-04-02 10:16:43 -0700169 logging.notice('Deleting chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800170 cros_build_lib.RunCommand(cmd, print_cmd=False)
171 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700172 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800173
174
Brian Harringae0a5322012-09-15 01:46:51 -0700175def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700176 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800177 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400178 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
179 # The os.ST_NOSUID constant wasn't added until python-3.2.
180 if st.f_flag & 0x2:
181 cros_build_lib.Die('chroot cannot be in a nosuid mount')
182
Brian Harringae0a5322012-09-15 01:46:51 -0700183 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800184 if chrome_root:
185 cmd.extend(['--chrome_root', chrome_root])
186 if chrome_root_mount:
187 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700188 if workspace:
189 cmd.extend(['--workspace_root', workspace])
190
Brian Harringb938c782012-02-29 15:14:38 -0800191 if len(additional_args) > 0:
192 cmd.append('--')
193 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700194
Ralph Nathan549d3502015-03-26 17:38:42 -0700195 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True,
196 mute_output=False)
Brian Harring7199e7d2012-03-23 04:10:08 -0700197 # If we were in interactive mode, ignore the exit code; it'll be whatever
198 # they last ran w/in the chroot and won't matter to us one way or another.
199 # Note this does allow chroot entrance to fail and be ignored during
200 # interactive; this is however a rare case and the user will immediately
201 # see it (nor will they be checking the exit code manually).
202 if ret.returncode != 0 and additional_args:
Richard Barnette5c728a42015-03-18 11:50:21 -0700203 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800204
205
David James56e6c2c2012-10-24 23:54:41 -0700206def _SudoCommand():
207 """Get the 'sudo' command, along with all needed environment variables."""
208
David James5a73b4d2013-03-07 10:23:40 -0800209 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
210 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700211 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800212 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700213 value = os.environ.get(key)
214 if value is not None:
215 cmd += ['%s=%s' % (key, value)]
216
217 # Pass in the path to the depot_tools so that users can access them from
218 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400219 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500220
David James56e6c2c2012-10-24 23:54:41 -0700221 return cmd
222
223
Josh Triplett472a4182013-03-08 11:48:57 -0800224def _ReportMissing(missing):
225 """Report missing utilities, then exit.
226
227 Args:
228 missing: List of missing utilities, as returned by
229 osutils.FindMissingBinaries. If non-empty, will not return.
230 """
231
232 if missing:
233 raise SystemExit(
234 'The tool(s) %s were not found.\n'
235 'Please install the appropriate package in your host.\n'
236 'Example(ubuntu):\n'
237 ' sudo apt-get install <packagename>'
238 % ', '.join(missing))
239
240
241def _ProxySimSetup(options):
242 """Set up proxy simulator, and return only in the child environment.
243
244 TODO: Ideally, this should support multiple concurrent invocations of
245 cros_sdk --proxy-sim; currently, such invocations will conflict with each
246 other due to the veth device names and IP addresses. Either this code would
247 need to generate fresh, unused names for all of these before forking, or it
248 would need to support multiple concurrent cros_sdk invocations sharing one
249 proxy and allowing it to exit when unused (without counting on any local
250 service-management infrastructure on the host).
251 """
252
253 may_need_mpm = False
254 apache_bin = osutils.Which('apache2')
255 if apache_bin is None:
256 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
257 if apache_bin is None:
258 _ReportMissing(('apache2',))
259 else:
260 may_need_mpm = True
261
262 # Module names and .so names included for ease of grepping.
263 apache_modules = [('proxy_module', 'mod_proxy.so'),
264 ('proxy_connect_module', 'mod_proxy_connect.so'),
265 ('proxy_http_module', 'mod_proxy_http.so'),
266 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
267
268 # Find the apache module directory, and make sure it has the modules we need.
269 module_dirs = {}
270 for g in PROXY_APACHE_MODULE_GLOBS:
271 for mod, so in apache_modules:
272 for f in glob.glob(os.path.join(g, so)):
273 module_dirs.setdefault(os.path.dirname(f), []).append(so)
274 for apache_module_path, modules_found in module_dirs.iteritems():
275 if len(modules_found) == len(apache_modules):
276 break
277 else:
278 # Appease cros lint, which doesn't understand that this else block will not
279 # fall through to the subsequent code which relies on apache_module_path.
280 apache_module_path = None
281 raise SystemExit(
282 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500283 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800284
285 def check_add_module(name):
286 so = 'mod_%s.so' % name
287 if os.access(os.path.join(apache_module_path, so), os.F_OK):
288 mod = '%s_module' % name
289 apache_modules.append((mod, so))
290 return True
291 return False
292
293 check_add_module('authz_core')
294 if may_need_mpm:
295 for mpm in PROXY_APACHE_MPMS:
296 if check_add_module('mpm_%s' % mpm):
297 break
298
299 veth_host = '%s-host' % PROXY_VETH_PREFIX
300 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
301
302 # Set up pipes from parent to child and vice versa.
303 # The child writes a byte to the parent after calling unshare, so that the
304 # parent can then assign the guest end of the veth interface to the child's
305 # new network namespace. The parent then writes a byte to the child after
306 # assigning the guest interface, so that the child can then configure that
307 # interface. In both cases, if we get back an EOF when reading from the
308 # pipe, we assume the other end exited with an error message, so just exit.
309 parent_readfd, child_writefd = os.pipe()
310 child_readfd, parent_writefd = os.pipe()
311 SUCCESS_FLAG = '+'
312
313 pid = os.fork()
314 if not pid:
315 os.close(parent_readfd)
316 os.close(parent_writefd)
317
318 namespaces.Unshare(namespaces.CLONE_NEWNET)
319 os.write(child_writefd, SUCCESS_FLAG)
320 os.close(child_writefd)
321 if os.read(child_readfd, 1) != SUCCESS_FLAG:
322 # Parent failed; it will have already have outputted an error message.
323 sys.exit(1)
324 os.close(child_readfd)
325
326 # Set up child side of the network.
327 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500328 ('ip', 'link', 'set', 'up', 'lo'),
329 ('ip', 'address', 'add',
330 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
331 'dev', veth_guest),
332 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800333 )
334 try:
335 for cmd in commands:
336 cros_build_lib.RunCommand(cmd, print_cmd=False)
337 except cros_build_lib.RunCommandError:
338 raise SystemExit('Running %r failed!' % (cmd,))
339
340 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
341 for proto in ('http', 'https', 'ftp'):
342 os.environ[proto + '_proxy'] = proxy_url
343 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
344 os.environ.pop(v, None)
345 return
346
347 os.close(child_readfd)
348 os.close(child_writefd)
349
350 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
351 # Child failed; it will have already have outputted an error message.
352 sys.exit(1)
353 os.close(parent_readfd)
354
355 # Set up parent side of the network.
356 uid = int(os.environ.get('SUDO_UID', '0'))
357 gid = int(os.environ.get('SUDO_GID', '0'))
358 if uid == 0 or gid == 0:
359 for username in PROXY_APACHE_FALLBACK_USERS:
360 try:
361 pwnam = pwd.getpwnam(username)
362 uid, gid = pwnam.pw_uid, pwnam.pw_gid
363 break
364 except KeyError:
365 continue
366 if uid == 0 or gid == 0:
367 raise SystemExit('Could not find a non-root user to run Apache as')
368
369 chroot_parent, chroot_base = os.path.split(options.chroot)
370 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
371 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
372
373 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500374 'User #%u' % uid,
375 'Group #%u' % gid,
376 'PidFile %s' % pid_file,
377 'ErrorLog %s' % log_file,
378 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
379 'ServerName %s' % PROXY_HOST_IP,
380 'ProxyRequests On',
381 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800382 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500383 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
384 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800385 ]
386 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500387 ('ip', 'link', 'add', 'name', veth_host,
388 'type', 'veth', 'peer', 'name', veth_guest),
389 ('ip', 'address', 'add',
390 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
391 'dev', veth_host),
392 ('ip', 'link', 'set', veth_host, 'up'),
393 ([apache_bin, '-f', '/dev/null'] +
394 [arg for d in apache_directives for arg in ('-C', d)]),
395 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800396 )
397 cmd = None # Make cros lint happy.
398 try:
399 for cmd in commands:
400 cros_build_lib.RunCommand(cmd, print_cmd=False)
401 except cros_build_lib.RunCommandError:
402 # Clean up existing interfaces, if any.
403 cmd_cleanup = ('ip', 'link', 'del', veth_host)
404 try:
405 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
406 except cros_build_lib.RunCommandError:
Ralph Nathan59900422015-03-24 10:41:17 -0700407 logging.error('running %r failed', cmd_cleanup)
Josh Triplett472a4182013-03-08 11:48:57 -0800408 raise SystemExit('Running %r failed!' % (cmd,))
409 os.write(parent_writefd, SUCCESS_FLAG)
410 os.close(parent_writefd)
411
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400412 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800413
414
Mike Frysingera78a56e2012-11-20 06:02:30 -0500415def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700416 """Re-execute cros_sdk as root.
417
418 Also unshare the mount namespace so as to ensure that processes outside
419 the chroot can't mess with our mounts.
420 """
421 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500422 cmd = _SudoCommand() + ['--'] + argv
423 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500424 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400425 # We must set up the cgroups mounts before we enter our own namespace.
426 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800427 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500428 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700429
430
Mike Frysinger34db8692013-11-11 14:54:08 -0500431def _CreateParser(sdk_latest_version, bootstrap_latest_version):
432 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700433 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800434
Brian Harring218e13c2012-10-10 16:21:26 -0700435This script is used for manipulating local chroot environments; creating,
436deleting, downloading, etc. If given --enter (or no args), it defaults
437to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800438
Brian Harring218e13c2012-10-10 16:21:26 -0700439If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700440
Brian Harring218e13c2012-10-10 16:21:26 -0700441 parser = commandline.OptionParser(usage=usage, caching=True)
442
Mike Frysinger34db8692013-11-11 14:54:08 -0500443 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500444 default_chroot = os.path.join(constants.SOURCE_ROOT,
445 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700446 parser.add_option(
447 '--chroot', dest='chroot', default=default_chroot, type='path',
448 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800449
Brian Harring218e13c2012-10-10 16:21:26 -0700450 parser.add_option('--chrome_root', default=None, type='path',
451 help='Mount this chrome root into the SDK chroot')
452 parser.add_option('--chrome_root_mount', default=None, type='path',
453 help='Mount chrome into this path inside SDK chroot')
454 parser.add_option('--nousepkg', action='store_true', default=False,
455 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800456 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700457 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800458 help=('''Use sdk tarball located at this url.
459 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700460 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500461 help=('Use this sdk version. For prebuilt, current is %r'
462 ', for bootstrapping it is %r.'
463 % (sdk_latest_version, bootstrap_latest_version)))
Don Garrett230d1b22015-03-09 16:21:19 -0700464 parser.add_option('--workspace', default=None,
465 help='Workspace directory to mount into the chroot.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500466
467 # Commands.
468 group = parser.add_option_group('Commands')
469 group.add_option(
470 '--enter', action='store_true', default=False,
471 help='Enter the SDK chroot. Implies --create.')
472 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500473 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500474 help='Create the chroot only if it does not already exist. '
475 'Implies --download.')
476 group.add_option(
477 '--bootstrap', action='store_true', default=False,
478 help='Build everything from scratch, including the sdk. '
479 'Use this only if you need to validate a change '
480 'that affects SDK creation itself (toolchain and '
481 'build are typically the only folk who need this). '
482 'Note this will quite heavily slow down the build. '
483 'This option implies --create --nousepkg.')
484 group.add_option(
485 '-r', '--replace', action='store_true', default=False,
486 help='Replace an existing SDK chroot. Basically an alias '
487 'for --delete --create.')
488 group.add_option(
489 '--delete', action='store_true', default=False,
490 help='Delete the current SDK chroot if it exists.')
491 group.add_option(
492 '--download', action='store_true', default=False,
493 help='Download the sdk.')
494 commands = group
495
Mike Frysinger80dfce92014-04-21 10:58:53 -0400496 # Namespace options.
497 group = parser.add_option_group('Namespaces')
498 group.add_option('--proxy-sim', action='store_true', default=False,
499 help='Simulate a restrictive network requiring an outbound'
500 ' proxy.')
501 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger41f28032014-09-29 16:53:41 -0500502 default=True, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400503 help='Do not create a new PID namespace.')
504
Mike Frysinger34db8692013-11-11 14:54:08 -0500505 # Internal options.
506 group = parser.add_option_group(
507 'Internal Chromium OS Build Team Options',
508 'Caution: these are for meant for the Chromium OS build team only')
509 group.add_option('--buildbot-log-version', default=False, action='store_true',
510 help='Log SDK version for buildbot consumption')
511
512 return (parser, commands)
513
514
515def main(argv):
516 conf = cros_build_lib.LoadKeyValueFile(
517 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
518 ignore_missing=True)
519 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
520 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
521 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700522 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800523
524 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500525 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800526
Brian Harring1790ac42012-09-23 08:53:33 -0700527 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700528 if host != 'x86_64':
529 parser.error(
530 "cros_sdk is currently only supported on x86_64; you're running"
531 " %s. Please find a x86_64 machine." % (host,))
532
Josh Triplett472a4182013-03-08 11:48:57 -0800533 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
534 if options.proxy_sim:
535 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800536
David James471532c2013-01-21 10:23:31 -0800537 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400538 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400539 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400540 else:
541 first_pid = None
David James471532c2013-01-21 10:23:31 -0800542
Brian Harring218e13c2012-10-10 16:21:26 -0700543 # Expand out the aliases...
544 if options.replace:
545 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800546
Brian Harring218e13c2012-10-10 16:21:26 -0700547 if options.bootstrap:
548 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800549
Brian Harring218e13c2012-10-10 16:21:26 -0700550 # If a command is not given, default to enter.
551 options.enter |= not any(getattr(options, x.dest)
552 for x in commands.option_list)
553 options.enter |= bool(chroot_command)
554
555 if options.enter and options.delete and not options.create:
556 parser.error("Trying to enter the chroot when --delete "
557 "was specified makes no sense.")
558
559 # Finally, discern if we need to create the chroot.
560 chroot_exists = os.path.exists(options.chroot)
561 if options.create or options.enter:
562 # Only create if it's being wiped, or if it doesn't exist.
563 if not options.delete and chroot_exists:
564 options.create = False
565 else:
566 options.download = True
567
568 # Finally, flip create if necessary.
569 if options.enter:
570 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800571
Brian Harringb938c782012-02-29 15:14:38 -0800572 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700573 sdk_version = (bootstrap_latest_version if options.bootstrap
574 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800575 else:
576 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500577 if options.buildbot_log_version:
578 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800579
Brian Harring1790ac42012-09-23 08:53:33 -0700580 # Based on selections, fetch the tarball.
581 if options.sdk_url:
582 urls = [options.sdk_url]
583 elif options.bootstrap:
584 urls = GetStage3Urls(sdk_version)
585 else:
586 urls = GetArchStageTarballs(sdk_version)
587
Brian Harringb6cf9142012-09-01 20:43:17 -0700588 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700589 lock_path = os.path.join(
590 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400591 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700592 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700593
Josh Triplett472a4182013-03-08 11:48:57 -0800594 if options.proxy_sim:
595 _ProxySimSetup(options)
596
David James56e6c2c2012-10-24 23:54:41 -0700597 if options.delete and os.path.exists(options.chroot):
598 lock.write_lock()
599 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800600
David James56e6c2c2012-10-24 23:54:41 -0700601 sdk_cache = os.path.join(options.cache_dir, 'sdks')
602 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700603 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700604
David James56e6c2c2012-10-24 23:54:41 -0700605 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500606 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700607 if not os.path.exists(src):
608 osutils.SafeMakedirs(target)
609 continue
610 lock.write_lock(
611 "Upgrade to %r needed but chroot is locked; please exit "
612 "all instances so this upgrade can finish." % src)
613 if not os.path.exists(src):
614 # Note that while waiting for the write lock, src may've vanished;
615 # it's a rare race during the upgrade process that's a byproduct
616 # of us avoiding taking a write lock to do the src check. If we
617 # took a write lock for that check, it would effectively limit
618 # all cros_sdk for a chroot to a single instance.
619 osutils.SafeMakedirs(target)
620 elif not os.path.exists(target):
621 # Upgrade occurred, but a reversion, or something whacky
622 # occurred writing to the old location. Wipe and continue.
623 os.rename(src, target)
624 else:
625 # Upgrade occurred once already, but either a reversion or
626 # some before/after separate cros_sdk usage is at play.
627 # Wipe and continue.
628 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700629
David James56e6c2c2012-10-24 23:54:41 -0700630 if options.download:
631 lock.write_lock()
632 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700633
David James56e6c2c2012-10-24 23:54:41 -0700634 if options.create:
635 lock.write_lock()
636 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
David Purselld99f6222015-05-04 16:52:59 -0700637 nousepkg=(options.bootstrap or options.nousepkg),
638 workspace=options.workspace)
Brian Harring1790ac42012-09-23 08:53:33 -0700639
David James56e6c2c2012-10-24 23:54:41 -0700640 if options.enter:
641 lock.read_lock()
642 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700643 options.chrome_root_mount, options.workspace,
644 chroot_command)