blob: 0a92c4e301627b8ff25af5a62e67c22226dcdf32 [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,
168 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])
180 if len(additional_args) > 0:
181 cmd.append('--')
182 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700183
184 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
185 # If we were in interactive mode, ignore the exit code; it'll be whatever
186 # they last ran w/in the chroot and won't matter to us one way or another.
187 # Note this does allow chroot entrance to fail and be ignored during
188 # interactive; this is however a rare case and the user will immediately
189 # see it (nor will they be checking the exit code manually).
190 if ret.returncode != 0 and additional_args:
191 raise SystemExit('Running %r failed with exit code %i'
192 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800193
194
David James56e6c2c2012-10-24 23:54:41 -0700195def _SudoCommand():
196 """Get the 'sudo' command, along with all needed environment variables."""
197
David James5a73b4d2013-03-07 10:23:40 -0800198 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
199 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700200 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800201 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700202 value = os.environ.get(key)
203 if value is not None:
204 cmd += ['%s=%s' % (key, value)]
205
206 # Pass in the path to the depot_tools so that users can access them from
207 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400208 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500209
David James56e6c2c2012-10-24 23:54:41 -0700210 return cmd
211
212
Josh Triplett472a4182013-03-08 11:48:57 -0800213def _ReportMissing(missing):
214 """Report missing utilities, then exit.
215
216 Args:
217 missing: List of missing utilities, as returned by
218 osutils.FindMissingBinaries. If non-empty, will not return.
219 """
220
221 if missing:
222 raise SystemExit(
223 'The tool(s) %s were not found.\n'
224 'Please install the appropriate package in your host.\n'
225 'Example(ubuntu):\n'
226 ' sudo apt-get install <packagename>'
227 % ', '.join(missing))
228
229
230def _ProxySimSetup(options):
231 """Set up proxy simulator, and return only in the child environment.
232
233 TODO: Ideally, this should support multiple concurrent invocations of
234 cros_sdk --proxy-sim; currently, such invocations will conflict with each
235 other due to the veth device names and IP addresses. Either this code would
236 need to generate fresh, unused names for all of these before forking, or it
237 would need to support multiple concurrent cros_sdk invocations sharing one
238 proxy and allowing it to exit when unused (without counting on any local
239 service-management infrastructure on the host).
240 """
241
242 may_need_mpm = False
243 apache_bin = osutils.Which('apache2')
244 if apache_bin is None:
245 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
246 if apache_bin is None:
247 _ReportMissing(('apache2',))
248 else:
249 may_need_mpm = True
250
251 # Module names and .so names included for ease of grepping.
252 apache_modules = [('proxy_module', 'mod_proxy.so'),
253 ('proxy_connect_module', 'mod_proxy_connect.so'),
254 ('proxy_http_module', 'mod_proxy_http.so'),
255 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
256
257 # Find the apache module directory, and make sure it has the modules we need.
258 module_dirs = {}
259 for g in PROXY_APACHE_MODULE_GLOBS:
260 for mod, so in apache_modules:
261 for f in glob.glob(os.path.join(g, so)):
262 module_dirs.setdefault(os.path.dirname(f), []).append(so)
263 for apache_module_path, modules_found in module_dirs.iteritems():
264 if len(modules_found) == len(apache_modules):
265 break
266 else:
267 # Appease cros lint, which doesn't understand that this else block will not
268 # fall through to the subsequent code which relies on apache_module_path.
269 apache_module_path = None
270 raise SystemExit(
271 'Could not find apache module path containing all required modules: %s'
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500272 % ', '.join(so for mod, so in apache_modules))
Josh Triplett472a4182013-03-08 11:48:57 -0800273
274 def check_add_module(name):
275 so = 'mod_%s.so' % name
276 if os.access(os.path.join(apache_module_path, so), os.F_OK):
277 mod = '%s_module' % name
278 apache_modules.append((mod, so))
279 return True
280 return False
281
282 check_add_module('authz_core')
283 if may_need_mpm:
284 for mpm in PROXY_APACHE_MPMS:
285 if check_add_module('mpm_%s' % mpm):
286 break
287
288 veth_host = '%s-host' % PROXY_VETH_PREFIX
289 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
290
291 # Set up pipes from parent to child and vice versa.
292 # The child writes a byte to the parent after calling unshare, so that the
293 # parent can then assign the guest end of the veth interface to the child's
294 # new network namespace. The parent then writes a byte to the child after
295 # assigning the guest interface, so that the child can then configure that
296 # interface. In both cases, if we get back an EOF when reading from the
297 # pipe, we assume the other end exited with an error message, so just exit.
298 parent_readfd, child_writefd = os.pipe()
299 child_readfd, parent_writefd = os.pipe()
300 SUCCESS_FLAG = '+'
301
302 pid = os.fork()
303 if not pid:
304 os.close(parent_readfd)
305 os.close(parent_writefd)
306
307 namespaces.Unshare(namespaces.CLONE_NEWNET)
308 os.write(child_writefd, SUCCESS_FLAG)
309 os.close(child_writefd)
310 if os.read(child_readfd, 1) != SUCCESS_FLAG:
311 # Parent failed; it will have already have outputted an error message.
312 sys.exit(1)
313 os.close(child_readfd)
314
315 # Set up child side of the network.
316 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500317 ('ip', 'link', 'set', 'up', 'lo'),
318 ('ip', 'address', 'add',
319 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
320 'dev', veth_guest),
321 ('ip', 'link', 'set', veth_guest, 'up'),
Josh Triplett472a4182013-03-08 11:48:57 -0800322 )
323 try:
324 for cmd in commands:
325 cros_build_lib.RunCommand(cmd, print_cmd=False)
326 except cros_build_lib.RunCommandError:
327 raise SystemExit('Running %r failed!' % (cmd,))
328
329 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
330 for proto in ('http', 'https', 'ftp'):
331 os.environ[proto + '_proxy'] = proxy_url
332 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
333 os.environ.pop(v, None)
334 return
335
336 os.close(child_readfd)
337 os.close(child_writefd)
338
339 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
340 # Child failed; it will have already have outputted an error message.
341 sys.exit(1)
342 os.close(parent_readfd)
343
344 # Set up parent side of the network.
345 uid = int(os.environ.get('SUDO_UID', '0'))
346 gid = int(os.environ.get('SUDO_GID', '0'))
347 if uid == 0 or gid == 0:
348 for username in PROXY_APACHE_FALLBACK_USERS:
349 try:
350 pwnam = pwd.getpwnam(username)
351 uid, gid = pwnam.pw_uid, pwnam.pw_gid
352 break
353 except KeyError:
354 continue
355 if uid == 0 or gid == 0:
356 raise SystemExit('Could not find a non-root user to run Apache as')
357
358 chroot_parent, chroot_base = os.path.split(options.chroot)
359 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
360 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
361
362 apache_directives = [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500363 'User #%u' % uid,
364 'Group #%u' % gid,
365 'PidFile %s' % pid_file,
366 'ErrorLog %s' % log_file,
367 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
368 'ServerName %s' % PROXY_HOST_IP,
369 'ProxyRequests On',
370 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
Josh Triplett472a4182013-03-08 11:48:57 -0800371 ] + [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500372 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
373 for (mod, so) in apache_modules
Josh Triplett472a4182013-03-08 11:48:57 -0800374 ]
375 commands = (
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500376 ('ip', 'link', 'add', 'name', veth_host,
377 'type', 'veth', 'peer', 'name', veth_guest),
378 ('ip', 'address', 'add',
379 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
380 'dev', veth_host),
381 ('ip', 'link', 'set', veth_host, 'up'),
382 ([apache_bin, '-f', '/dev/null'] +
383 [arg for d in apache_directives for arg in ('-C', d)]),
384 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
Josh Triplett472a4182013-03-08 11:48:57 -0800385 )
386 cmd = None # Make cros lint happy.
387 try:
388 for cmd in commands:
389 cros_build_lib.RunCommand(cmd, print_cmd=False)
390 except cros_build_lib.RunCommandError:
391 # Clean up existing interfaces, if any.
392 cmd_cleanup = ('ip', 'link', 'del', veth_host)
393 try:
394 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
395 except cros_build_lib.RunCommandError:
396 cros_build_lib.Error('running %r failed', cmd_cleanup)
397 raise SystemExit('Running %r failed!' % (cmd,))
398 os.write(parent_writefd, SUCCESS_FLAG)
399 os.close(parent_writefd)
400
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400401 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800402
403
Mike Frysingera78a56e2012-11-20 06:02:30 -0500404def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700405 """Re-execute cros_sdk as root.
406
407 Also unshare the mount namespace so as to ensure that processes outside
408 the chroot can't mess with our mounts.
409 """
410 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500411 cmd = _SudoCommand() + ['--'] + argv
412 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500413 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400414 # We must set up the cgroups mounts before we enter our own namespace.
415 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800416 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400417 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700418
419
Mike Frysinger34db8692013-11-11 14:54:08 -0500420def _CreateParser(sdk_latest_version, bootstrap_latest_version):
421 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700422 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800423
Brian Harring218e13c2012-10-10 16:21:26 -0700424This script is used for manipulating local chroot environments; creating,
425deleting, downloading, etc. If given --enter (or no args), it defaults
426to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800427
Brian Harring218e13c2012-10-10 16:21:26 -0700428If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700429
Brian Harring218e13c2012-10-10 16:21:26 -0700430 parser = commandline.OptionParser(usage=usage, caching=True)
431
Mike Frysinger34db8692013-11-11 14:54:08 -0500432 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500433 default_chroot = os.path.join(constants.SOURCE_ROOT,
434 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700435 parser.add_option(
436 '--chroot', dest='chroot', default=default_chroot, type='path',
437 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800438
Brian Harring218e13c2012-10-10 16:21:26 -0700439 parser.add_option('--chrome_root', default=None, type='path',
440 help='Mount this chrome root into the SDK chroot')
441 parser.add_option('--chrome_root_mount', default=None, type='path',
442 help='Mount chrome into this path inside SDK chroot')
443 parser.add_option('--nousepkg', action='store_true', default=False,
444 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800445 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700446 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800447 help=('''Use sdk tarball located at this url.
448 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700449 parser.add_option('--sdk-version', default=None,
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500450 help=('Use this sdk version. For prebuilt, current is %r'
451 ', for bootstrapping it is %r.'
452 % (sdk_latest_version, bootstrap_latest_version)))
Mike Frysinger34db8692013-11-11 14:54:08 -0500453
454 # Commands.
455 group = parser.add_option_group('Commands')
456 group.add_option(
457 '--enter', action='store_true', default=False,
458 help='Enter the SDK chroot. Implies --create.')
459 group.add_option(
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500460 '--create', action='store_true', default=False,
Mike Frysinger34db8692013-11-11 14:54:08 -0500461 help='Create the chroot only if it does not already exist. '
462 'Implies --download.')
463 group.add_option(
464 '--bootstrap', action='store_true', default=False,
465 help='Build everything from scratch, including the sdk. '
466 'Use this only if you need to validate a change '
467 'that affects SDK creation itself (toolchain and '
468 'build are typically the only folk who need this). '
469 'Note this will quite heavily slow down the build. '
470 'This option implies --create --nousepkg.')
471 group.add_option(
472 '-r', '--replace', action='store_true', default=False,
473 help='Replace an existing SDK chroot. Basically an alias '
474 'for --delete --create.')
475 group.add_option(
476 '--delete', action='store_true', default=False,
477 help='Delete the current SDK chroot if it exists.')
478 group.add_option(
479 '--download', action='store_true', default=False,
480 help='Download the sdk.')
481 commands = group
482
Mike Frysinger80dfce92014-04-21 10:58:53 -0400483 # Namespace options.
484 group = parser.add_option_group('Namespaces')
485 group.add_option('--proxy-sim', action='store_true', default=False,
486 help='Simulate a restrictive network requiring an outbound'
487 ' proxy.')
Mike Frysinger94b32a12014-09-12 14:28:13 -0400488 # TODO(vapier): Turn off pid ns until ccache issues can be fixed.
489 # http://crbug.com/411984
Mike Frysinger80dfce92014-04-21 10:58:53 -0400490 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger94b32a12014-09-12 14:28:13 -0400491 default=False, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400492 help='Do not create a new PID namespace.')
493
Mike Frysinger34db8692013-11-11 14:54:08 -0500494 # Internal options.
495 group = parser.add_option_group(
496 'Internal Chromium OS Build Team Options',
497 'Caution: these are for meant for the Chromium OS build team only')
498 group.add_option('--buildbot-log-version', default=False, action='store_true',
499 help='Log SDK version for buildbot consumption')
500
501 return (parser, commands)
502
503
504def main(argv):
505 conf = cros_build_lib.LoadKeyValueFile(
506 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
507 ignore_missing=True)
508 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
509 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
510 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700511 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800512
513 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500514 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800515
Brian Harring1790ac42012-09-23 08:53:33 -0700516 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700517 if host != 'x86_64':
518 parser.error(
519 "cros_sdk is currently only supported on x86_64; you're running"
520 " %s. Please find a x86_64 machine." % (host,))
521
Josh Triplett472a4182013-03-08 11:48:57 -0800522 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
523 if options.proxy_sim:
524 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800525
David James471532c2013-01-21 10:23:31 -0800526 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400527 if options.ns_pid:
Mike Frysingere2d8f0d2014-11-01 13:09:26 -0400528 first_pid = namespaces.CreatePidNs()
Mike Frysinger80dfce92014-04-21 10:58:53 -0400529 else:
530 first_pid = None
David James471532c2013-01-21 10:23:31 -0800531
Brian Harring218e13c2012-10-10 16:21:26 -0700532 # Expand out the aliases...
533 if options.replace:
534 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800535
Brian Harring218e13c2012-10-10 16:21:26 -0700536 if options.bootstrap:
537 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800538
Brian Harring218e13c2012-10-10 16:21:26 -0700539 # If a command is not given, default to enter.
540 options.enter |= not any(getattr(options, x.dest)
541 for x in commands.option_list)
542 options.enter |= bool(chroot_command)
543
544 if options.enter and options.delete and not options.create:
545 parser.error("Trying to enter the chroot when --delete "
546 "was specified makes no sense.")
547
548 # Finally, discern if we need to create the chroot.
549 chroot_exists = os.path.exists(options.chroot)
550 if options.create or options.enter:
551 # Only create if it's being wiped, or if it doesn't exist.
552 if not options.delete and chroot_exists:
553 options.create = False
554 else:
555 options.download = True
556
557 # Finally, flip create if necessary.
558 if options.enter:
559 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800560
Brian Harringb938c782012-02-29 15:14:38 -0800561 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700562 sdk_version = (bootstrap_latest_version if options.bootstrap
563 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800564 else:
565 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500566 if options.buildbot_log_version:
567 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800568
Brian Harring1790ac42012-09-23 08:53:33 -0700569 # Based on selections, fetch the tarball.
570 if options.sdk_url:
571 urls = [options.sdk_url]
572 elif options.bootstrap:
573 urls = GetStage3Urls(sdk_version)
574 else:
575 urls = GetArchStageTarballs(sdk_version)
576
Brian Harringb6cf9142012-09-01 20:43:17 -0700577 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800578 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700579 '.%s_lock' % os.path.basename(options.chroot))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400580 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700581 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700582
Josh Triplett472a4182013-03-08 11:48:57 -0800583 if options.proxy_sim:
584 _ProxySimSetup(options)
585
David James56e6c2c2012-10-24 23:54:41 -0700586 if options.delete and os.path.exists(options.chroot):
587 lock.write_lock()
588 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800589
David James56e6c2c2012-10-24 23:54:41 -0700590 sdk_cache = os.path.join(options.cache_dir, 'sdks')
591 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700592 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700593
David James56e6c2c2012-10-24 23:54:41 -0700594 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500595 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700596 if not os.path.exists(src):
597 osutils.SafeMakedirs(target)
598 continue
599 lock.write_lock(
600 "Upgrade to %r needed but chroot is locked; please exit "
601 "all instances so this upgrade can finish." % src)
602 if not os.path.exists(src):
603 # Note that while waiting for the write lock, src may've vanished;
604 # it's a rare race during the upgrade process that's a byproduct
605 # of us avoiding taking a write lock to do the src check. If we
606 # took a write lock for that check, it would effectively limit
607 # all cros_sdk for a chroot to a single instance.
608 osutils.SafeMakedirs(target)
609 elif not os.path.exists(target):
610 # Upgrade occurred, but a reversion, or something whacky
611 # occurred writing to the old location. Wipe and continue.
612 os.rename(src, target)
613 else:
614 # Upgrade occurred once already, but either a reversion or
615 # some before/after separate cros_sdk usage is at play.
616 # Wipe and continue.
617 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700618
David James56e6c2c2012-10-24 23:54:41 -0700619 if options.download:
620 lock.write_lock()
621 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700622
David James56e6c2c2012-10-24 23:54:41 -0700623 if options.create:
624 lock.write_lock()
625 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
626 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700627
David James56e6c2c2012-10-24 23:54:41 -0700628 if options.enter:
629 lock.read_lock()
630 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
631 options.chrome_root_mount, chroot_command)