blob: 681d5a08938506fea79760a75bb71af1404c4be4 [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
Brian Harringb938c782012-02-29 15:14:38 -080019from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080020from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070021from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040022from chromite.lib import process_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080023from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050024from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080025
26cros_build_lib.STRICT_SUDO = True
27
28
Zdenek Behanaa52cea2012-05-30 01:31:11 +020029COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020030
Brian Harringb938c782012-02-29 15:14:38 -080031# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050032MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
33 'src/scripts/sdk_lib/make_chroot.sh')]
34ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
35 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080036
Josh Triplett472a4182013-03-08 11:48:57 -080037# Proxy simulator configuration.
38PROXY_HOST_IP = '192.168.240.1'
39PROXY_PORT = 8080
40PROXY_GUEST_IP = '192.168.240.2'
41PROXY_NETMASK = 30
42PROXY_VETH_PREFIX = 'veth'
43PROXY_CONNECT_PORTS = (80, 443, 9418)
44PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
45PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
46PROXY_APACHE_FALLBACK_PATH = ':'.join(
47 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
48)
49PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
50
Josh Triplett9a495f62013-03-15 18:06:55 -070051# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080052NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080053
Josh Triplett472a4182013-03-08 11:48:57 -080054# Tools needed for --proxy-sim only.
55PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080056
Mike Frysingercc838832014-05-24 13:10:30 -040057
Brian Harring1790ac42012-09-23 08:53:33 -070058def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080059 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070060 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050061 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
62 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070063 for compressor in COMPRESSION_PREFERENCE]
64
65
66def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050067 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070068 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080069
70
Brian Harringae0a5322012-09-15 01:46:51 -070071def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050072 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020073
74 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050075 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020076 urls: List of URLs to try to download. Download will stop on first success.
77
78 Returns:
79 Full path to the downloaded file
80 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020081
Brian Harring1790ac42012-09-23 08:53:33 -070082 # Note we track content length ourselves since certain versions of curl
83 # fail if asked to resume a complete file.
84 # pylint: disable=C0301,W0631
85 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020086 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070087 # http://www.logilab.org/ticket/8766
88 # pylint: disable=E1101
89 parsed = urlparse.urlparse(url)
90 tarball_name = os.path.basename(parsed.path)
91 if parsed.scheme in ('', 'file'):
92 if os.path.exists(parsed.path):
93 return parsed.path
94 continue
95 content_length = 0
Mike Frysinger383367e2014-09-16 15:06:17 -040096 print('Attempting download: %s' % url)
David Jamesc93e6a4d2014-01-13 11:37:36 -080097 result = retry_util.RunCurl(
Mike Frysingerd6e2df02014-11-26 02:55:04 -050098 ['-I', url], redirect_stdout=True, redirect_stderr=True,
99 print_cmd=False)
Brian Harring1790ac42012-09-23 08:53:33 -0700100 successful = False
101 for header in result.output.splitlines():
102 # We must walk the output to find the string '200 OK' for use cases where
103 # a proxy is involved and may have pushed down the actual header.
104 if header.find('200 OK') != -1:
105 successful = True
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500106 elif header.lower().startswith('content-length:'):
107 content_length = int(header.split(':', 1)[-1].strip())
Brian Harring1790ac42012-09-23 08:53:33 -0700108 if successful:
109 break
110 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111 break
112 else:
113 raise Exception('No valid URLs found!')
114
Brian Harringae0a5322012-09-15 01:46:51 -0700115 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700116 current_size = 0
117 if os.path.exists(tarball_dest):
118 current_size = os.path.getsize(tarball_dest)
119 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700120 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700121 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100122
Brian Harring1790ac42012-09-23 08:53:33 -0700123 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800124 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700125 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
126 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800127
Brian Harring1790ac42012-09-23 08:53:33 -0700128 # Cleanup old tarballs now since we've successfull fetched; only cleanup
129 # the tarballs for our prefix, or unknown ones.
130 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
131 else 'cros-sdk-')
132 for filename in os.listdir(storage_dir):
133 if filename == tarball_name or filename.startswith(ignored_prefix):
134 continue
Brian Harringb938c782012-02-29 15:14:38 -0800135
Mike Frysinger383367e2014-09-16 15:06:17 -0400136 print('Cleaning up old tarball: %s' % (filename,))
David James56e6c2c2012-10-24 23:54:41 -0700137 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200138
Brian Harringb938c782012-02-29 15:14:38 -0800139 return tarball_dest
140
141
Brian Harring1790ac42012-09-23 08:53:33 -0700142def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800143 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800144
Brian Harring1790ac42012-09-23 08:53:33 -0700145 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700146 '--chroot', chroot_path,
147 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400148 if nousepkg:
149 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800150
151 try:
152 cros_build_lib.RunCommand(cmd, print_cmd=False)
153 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700154 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800155
156
157def DeleteChroot(chroot_path):
158 """Deletes an existing chroot"""
159 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
160 '--delete']
161 try:
162 cros_build_lib.RunCommand(cmd, print_cmd=False)
163 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700164 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800165
166
Brian Harringae0a5322012-09-15 01:46:51 -0700167def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
Don Garrett230d1b22015-03-09 16:21:19 -0700168 workspace, additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800169 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400170 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
171 # The os.ST_NOSUID constant wasn't added until python-3.2.
172 if st.f_flag & 0x2:
173 cros_build_lib.Die('chroot cannot be in a nosuid mount')
174
Brian Harringae0a5322012-09-15 01:46:51 -0700175 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800176 if chrome_root:
177 cmd.extend(['--chrome_root', chrome_root])
178 if chrome_root_mount:
179 cmd.extend(['--chrome_root_mount', chrome_root_mount])
Don Garrett230d1b22015-03-09 16:21:19 -0700180 if workspace:
181 cmd.extend(['--workspace_root', workspace])
182
Brian Harringb938c782012-02-29 15:14:38 -0800183 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:
Richard Barnette5c728a42015-03-18 11:50:21 -0700194 raise SystemExit(ret.returncode)
Brian Harringb938c782012-02-29 15:14:38 -0800195
196
David James56e6c2c2012-10-24 23:54:41 -0700197def _SudoCommand():
198 """Get the 'sudo' command, along with all needed environment variables."""
199
David James5a73b4d2013-03-07 10:23:40 -0800200 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
201 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700202 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800203 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700204 value = os.environ.get(key)
205 if value is not None:
206 cmd += ['%s=%s' % (key, value)]
207
208 # Pass in the path to the depot_tools so that users can access them from
209 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400210 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500211
David James56e6c2c2012-10-24 23:54:41 -0700212 return cmd
213
214
Josh Triplett472a4182013-03-08 11:48:57 -0800215def _ReportMissing(missing):
216 """Report missing utilities, then exit.
217
218 Args:
219 missing: List of missing utilities, as returned by
220 osutils.FindMissingBinaries. If non-empty, will not return.
221 """
222
223 if missing:
224 raise SystemExit(
225 'The tool(s) %s were not found.\n'
226 'Please install the appropriate package in your host.\n'
227 'Example(ubuntu):\n'
228 ' sudo apt-get install <packagename>'
229 % ', '.join(missing))
230
231
232def _ProxySimSetup(options):
233 """Set up proxy simulator, and return only in the child environment.
234
235 TODO: Ideally, this should support multiple concurrent invocations of
236 cros_sdk --proxy-sim; currently, such invocations will conflict with each
237 other due to the veth device names and IP addresses. Either this code would
238 need to generate fresh, unused names for all of these before forking, or it
239 would need to support multiple concurrent cros_sdk invocations sharing one
240 proxy and allowing it to exit when unused (without counting on any local
241 service-management infrastructure on the host).
242 """
243
244 may_need_mpm = False
245 apache_bin = osutils.Which('apache2')
246 if apache_bin is None:
247 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
248 if apache_bin is None:
249 _ReportMissing(('apache2',))
250 else:
251 may_need_mpm = True
252
253 # Module names and .so names included for ease of grepping.
254 apache_modules = [('proxy_module', 'mod_proxy.so'),
255 ('proxy_connect_module', 'mod_proxy_connect.so'),
256 ('proxy_http_module', 'mod_proxy_http.so'),
257 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
258
259 # Find the apache module directory, and make sure it has the modules we need.
260 module_dirs = {}
261 for g in PROXY_APACHE_MODULE_GLOBS:
262 for mod, so in apache_modules:
263 for f in glob.glob(os.path.join(g, so)):
264 module_dirs.setdefault(os.path.dirname(f), []).append(so)
265 for apache_module_path, modules_found in module_dirs.iteritems():
266 if len(modules_found) == len(apache_modules):
267 break
268 else:
269 # Appease cros lint, which doesn't understand that this else block will not
270 # fall through to the subsequent code which relies on apache_module_path.
271 apache_module_path = None
272 raise SystemExit(
273 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500274 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800275
276 def check_add_module(name):
277 so = 'mod_%s.so' % name
278 if os.access(os.path.join(apache_module_path, so), os.F_OK):
279 mod = '%s_module' % name
280 apache_modules.append((mod, so))
281 return True
282 return False
283
284 check_add_module('authz_core')
285 if may_need_mpm:
286 for mpm in PROXY_APACHE_MPMS:
287 if check_add_module('mpm_%s' % mpm):
288 break
289
290 veth_host = '%s-host' % PROXY_VETH_PREFIX
291 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
292
293 # Set up pipes from parent to child and vice versa.
294 # The child writes a byte to the parent after calling unshare, so that the
295 # parent can then assign the guest end of the veth interface to the child's
296 # new network namespace. The parent then writes a byte to the child after
297 # assigning the guest interface, so that the child can then configure that
298 # interface. In both cases, if we get back an EOF when reading from the
299 # pipe, we assume the other end exited with an error message, so just exit.
300 parent_readfd, child_writefd = os.pipe()
301 child_readfd, parent_writefd = os.pipe()
302 SUCCESS_FLAG = '+'
303
304 pid = os.fork()
305 if not pid:
306 os.close(parent_readfd)
307 os.close(parent_writefd)
308
309 namespaces.Unshare(namespaces.CLONE_NEWNET)
310 os.write(child_writefd, SUCCESS_FLAG)
311 os.close(child_writefd)
312 if os.read(child_readfd, 1) != SUCCESS_FLAG:
313 # Parent failed; it will have already have outputted an error message.
314 sys.exit(1)
315 os.close(child_readfd)
316
317 # Set up child side of the network.
318 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500319 ('ip', 'link', 'set', 'up', 'lo'),
320 ('ip', 'address', 'add',
321 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
322 'dev', veth_guest),
323 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800324 )
325 try:
326 for cmd in commands:
327 cros_build_lib.RunCommand(cmd, print_cmd=False)
328 except cros_build_lib.RunCommandError:
329 raise SystemExit('Running %r failed!' % (cmd,))
330
331 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
332 for proto in ('http', 'https', 'ftp'):
333 os.environ[proto + '_proxy'] = proxy_url
334 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
335 os.environ.pop(v, None)
336 return
337
338 os.close(child_readfd)
339 os.close(child_writefd)
340
341 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
342 # Child failed; it will have already have outputted an error message.
343 sys.exit(1)
344 os.close(parent_readfd)
345
346 # Set up parent side of the network.
347 uid = int(os.environ.get('SUDO_UID', '0'))
348 gid = int(os.environ.get('SUDO_GID', '0'))
349 if uid == 0 or gid == 0:
350 for username in PROXY_APACHE_FALLBACK_USERS:
351 try:
352 pwnam = pwd.getpwnam(username)
353 uid, gid = pwnam.pw_uid, pwnam.pw_gid
354 break
355 except KeyError:
356 continue
357 if uid == 0 or gid == 0:
358 raise SystemExit('Could not find a non-root user to run Apache as')
359
360 chroot_parent, chroot_base = os.path.split(options.chroot)
361 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
362 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
363
364 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500365 'User #%u' % uid,
366 'Group #%u' % gid,
367 'PidFile %s' % pid_file,
368 'ErrorLog %s' % log_file,
369 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
370 'ServerName %s' % PROXY_HOST_IP,
371 'ProxyRequests On',
372 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800373 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500374 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
375 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800376 ]
377 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500378 ('ip', 'link', 'add', 'name', veth_host,
379 'type', 'veth', 'peer', 'name', veth_guest),
380 ('ip', 'address', 'add',
381 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
382 'dev', veth_host),
383 ('ip', 'link', 'set', veth_host, 'up'),
384 ([apache_bin, '-f', '/dev/null'] +
385 [arg for d in apache_directives for arg in ('-C', d)]),
386 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800387 )
388 cmd = None # Make cros lint happy.
389 try:
390 for cmd in commands:
391 cros_build_lib.RunCommand(cmd, print_cmd=False)
392 except cros_build_lib.RunCommandError:
393 # Clean up existing interfaces, if any.
394 cmd_cleanup = ('ip', 'link', 'del', veth_host)
395 try:
396 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
397 except cros_build_lib.RunCommandError:
398 cros_build_lib.Error('running %r failed', cmd_cleanup)
399 raise SystemExit('Running %r failed!' % (cmd,))
400 os.write(parent_writefd, SUCCESS_FLAG)
401 os.close(parent_writefd)
402
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400403 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800404
405
Mike Frysingera78a56e2012-11-20 06:02:30 -0500406def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700407 """Re-execute cros_sdk as root.
408
409 Also unshare the mount namespace so as to ensure that processes outside
410 the chroot can't mess with our mounts.
411 """
412 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500413 cmd = _SudoCommand() + ['--'] + argv
414 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500415 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400416 # We must set up the cgroups mounts before we enter our own namespace.
417 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800418 cgroups.Cgroup.InitSystem()
Mike Frysinger7a5fd842014-11-26 18:10:07 -0500419 namespaces.SimpleUnshare()
David James56e6c2c2012-10-24 23:54:41 -0700420
421
Mike Frysinger34db8692013-11-11 14:54:08 -0500422def _CreateParser(sdk_latest_version, bootstrap_latest_version):
423 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700424 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800425
Brian Harring218e13c2012-10-10 16:21:26 -0700426This script is used for manipulating local chroot environments; creating,
427deleting, downloading, etc. If given --enter (or no args), it defaults
428to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800429
Brian Harring218e13c2012-10-10 16:21:26 -0700430If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700431
Brian Harring218e13c2012-10-10 16:21:26 -0700432 parser = commandline.OptionParser(usage=usage, caching=True)
433
Mike Frysinger34db8692013-11-11 14:54:08 -0500434 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500435 default_chroot = os.path.join(constants.SOURCE_ROOT,
436 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700437 parser.add_option(
438 '--chroot', dest='chroot', default=default_chroot, type='path',
439 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800440
Brian Harring218e13c2012-10-10 16:21:26 -0700441 parser.add_option('--chrome_root', default=None, type='path',
442 help='Mount this chrome root into the SDK chroot')
443 parser.add_option('--chrome_root_mount', default=None, type='path',
444 help='Mount chrome into this path inside SDK chroot')
445 parser.add_option('--nousepkg', action='store_true', default=False,
446 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800447 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700448 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800449 help=('''Use sdk tarball located at this url.
450 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700451 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500452 help=('Use this sdk version. For prebuilt, current is %r'
453 ', for bootstrapping it is %r.'
454 % (sdk_latest_version, bootstrap_latest_version)))
Don Garrett230d1b22015-03-09 16:21:19 -0700455 parser.add_option('--workspace', default=None,
456 help='Workspace directory to mount into the chroot.')
Mike Frysinger34db8692013-11-11 14:54:08 -0500457
458 # Commands.
459 group = parser.add_option_group('Commands')
460 group.add_option(
461 '--enter', action='store_true', default=False,
462 help='Enter the SDK chroot. Implies --create.')
463 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500464 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500465 help='Create the chroot only if it does not already exist. '
466 'Implies --download.')
467 group.add_option(
468 '--bootstrap', action='store_true', default=False,
469 help='Build everything from scratch, including the sdk. '
470 'Use this only if you need to validate a change '
471 'that affects SDK creation itself (toolchain and '
472 'build are typically the only folk who need this). '
473 'Note this will quite heavily slow down the build. '
474 'This option implies --create --nousepkg.')
475 group.add_option(
476 '-r', '--replace', action='store_true', default=False,
477 help='Replace an existing SDK chroot. Basically an alias '
478 'for --delete --create.')
479 group.add_option(
480 '--delete', action='store_true', default=False,
481 help='Delete the current SDK chroot if it exists.')
482 group.add_option(
483 '--download', action='store_true', default=False,
484 help='Download the sdk.')
485 commands = group
486
Mike Frysinger80dfce92014-04-21 10:58:53 -0400487 # Namespace options.
488 group = parser.add_option_group('Namespaces')
489 group.add_option('--proxy-sim', action='store_true', default=False,
490 help='Simulate a restrictive network requiring an outbound'
491 ' proxy.')
492 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger41f28032014-09-29 16:53:41 -0500493 default=True, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400494 help='Do not create a new PID namespace.')
495
Mike Frysinger34db8692013-11-11 14:54:08 -0500496 # Internal options.
497 group = parser.add_option_group(
498 'Internal Chromium OS Build Team Options',
499 'Caution: these are for meant for the Chromium OS build team only')
500 group.add_option('--buildbot-log-version', default=False, action='store_true',
501 help='Log SDK version for buildbot consumption')
502
503 return (parser, commands)
504
505
506def main(argv):
507 conf = cros_build_lib.LoadKeyValueFile(
508 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
509 ignore_missing=True)
510 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
511 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
512 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700513 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800514
515 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500516 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800517
Brian Harring1790ac42012-09-23 08:53:33 -0700518 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700519 if host != 'x86_64':
520 parser.error(
521 "cros_sdk is currently only supported on x86_64; you're running"
522 " %s. Please find a x86_64 machine." % (host,))
523
Josh Triplett472a4182013-03-08 11:48:57 -0800524 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
525 if options.proxy_sim:
526 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800527
David James471532c2013-01-21 10:23:31 -0800528 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400529 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400530 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400531 else:
532 first_pid = None
David James471532c2013-01-21 10:23:31 -0800533
Brian Harring218e13c2012-10-10 16:21:26 -0700534 # Expand out the aliases...
535 if options.replace:
536 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800537
Brian Harring218e13c2012-10-10 16:21:26 -0700538 if options.bootstrap:
539 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800540
Brian Harring218e13c2012-10-10 16:21:26 -0700541 # If a command is not given, default to enter.
542 options.enter |= not any(getattr(options, x.dest)
543 for x in commands.option_list)
544 options.enter |= bool(chroot_command)
545
546 if options.enter and options.delete and not options.create:
547 parser.error("Trying to enter the chroot when --delete "
548 "was specified makes no sense.")
549
550 # Finally, discern if we need to create the chroot.
551 chroot_exists = os.path.exists(options.chroot)
552 if options.create or options.enter:
553 # Only create if it's being wiped, or if it doesn't exist.
554 if not options.delete and chroot_exists:
555 options.create = False
556 else:
557 options.download = True
558
559 # Finally, flip create if necessary.
560 if options.enter:
561 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800562
Brian Harringb938c782012-02-29 15:14:38 -0800563 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700564 sdk_version = (bootstrap_latest_version if options.bootstrap
565 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800566 else:
567 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500568 if options.buildbot_log_version:
569 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800570
Brian Harring1790ac42012-09-23 08:53:33 -0700571 # Based on selections, fetch the tarball.
572 if options.sdk_url:
573 urls = [options.sdk_url]
574 elif options.bootstrap:
575 urls = GetStage3Urls(sdk_version)
576 else:
577 urls = GetArchStageTarballs(sdk_version)
578
Brian Harringb6cf9142012-09-01 20:43:17 -0700579 lock_path = os.path.dirname(options.chroot)
Gilad Arnoldfbe40e22015-03-18 14:52:16 -0700580 lock_path = os.path.join(
581 lock_path, '.%s_lock' % os.path.basename(options.chroot).lstrip('.'))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400582 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700583 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700584
Josh Triplett472a4182013-03-08 11:48:57 -0800585 if options.proxy_sim:
586 _ProxySimSetup(options)
587
David James56e6c2c2012-10-24 23:54:41 -0700588 if options.delete and os.path.exists(options.chroot):
589 lock.write_lock()
590 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800591
David James56e6c2c2012-10-24 23:54:41 -0700592 sdk_cache = os.path.join(options.cache_dir, 'sdks')
593 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700594 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700595
David James56e6c2c2012-10-24 23:54:41 -0700596 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500597 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700598 if not os.path.exists(src):
599 osutils.SafeMakedirs(target)
600 continue
601 lock.write_lock(
602 "Upgrade to %r needed but chroot is locked; please exit "
603 "all instances so this upgrade can finish." % src)
604 if not os.path.exists(src):
605 # Note that while waiting for the write lock, src may've vanished;
606 # it's a rare race during the upgrade process that's a byproduct
607 # of us avoiding taking a write lock to do the src check. If we
608 # took a write lock for that check, it would effectively limit
609 # all cros_sdk for a chroot to a single instance.
610 osutils.SafeMakedirs(target)
611 elif not os.path.exists(target):
612 # Upgrade occurred, but a reversion, or something whacky
613 # occurred writing to the old location. Wipe and continue.
614 os.rename(src, target)
615 else:
616 # Upgrade occurred once already, but either a reversion or
617 # some before/after separate cros_sdk usage is at play.
618 # Wipe and continue.
619 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700620
David James56e6c2c2012-10-24 23:54:41 -0700621 if options.download:
622 lock.write_lock()
623 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700624
David James56e6c2c2012-10-24 23:54:41 -0700625 if options.create:
626 lock.write_lock()
627 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
628 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700629
David James56e6c2c2012-10-24 23:54:41 -0700630 if options.enter:
631 lock.read_lock()
632 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Don Garrett230d1b22015-03-09 16:21:19 -0700633 options.chrome_root_mount, options.workspace,
634 chroot_command)