blob: 0ec47a769a66d273bf76958ee1c883bbfe6f96c7 [file] [log] [blame]
Brian Harringb938c782012-02-29 15:14:38 -08001#!/usr/bin/env python
Mike Frysinger2de7f042012-07-10 04:45:03 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Mike Frysinger80dfce92014-04-21 10:58:53 -04006"""This script fetches and prepares an SDK chroot."""
Brian Harringb938c782012-02-29 15:14:38 -08007
Mike Frysinger80dfce92014-04-21 10:58:53 -04008import errno
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
Mike Frysinger80dfce92014-04-21 10:58:53 -040012import signal
David James56e6c2c2012-10-24 23:54:41 -070013import sys
Mike Frysinger80dfce92014-04-21 10:58:53 -040014import time
Brian Harringb938c782012-02-29 15:14:38 -080015import urlparse
16
Don Garrett88b8d782014-05-13 17:30:55 -070017from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080018from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070019from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080020from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080021from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080022from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070023from chromite.lib import osutils
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
Zdenek Behanfd0efe42012-04-13 04:36:40 +020087 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070088 # http://www.logilab.org/ticket/8766
89 # pylint: disable=E1101
90 parsed = urlparse.urlparse(url)
91 tarball_name = os.path.basename(parsed.path)
92 if parsed.scheme in ('', 'file'):
93 if os.path.exists(parsed.path):
94 return parsed.path
95 continue
96 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020097 print 'Attempting download: %s' % url
David Jamesc93e6a4d2014-01-13 11:37:36 -080098 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -070099 ['-I', url], redirect_stdout=True, redirect_stderr=True,
100 print_cmd=False)
101 successful = False
102 for header in result.output.splitlines():
103 # We must walk the output to find the string '200 OK' for use cases where
104 # a proxy is involved and may have pushed down the actual header.
105 if header.find('200 OK') != -1:
106 successful = True
107 elif header.lower().startswith("content-length:"):
108 content_length = int(header.split(":", 1)[-1].strip())
109 if successful:
110 break
111 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200112 break
113 else:
114 raise Exception('No valid URLs found!')
115
Brian Harringae0a5322012-09-15 01:46:51 -0700116 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700117 current_size = 0
118 if os.path.exists(tarball_dest):
119 current_size = os.path.getsize(tarball_dest)
120 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700121 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700122 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100123
Brian Harring1790ac42012-09-23 08:53:33 -0700124 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800125 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700126 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
127 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800128
Brian Harring1790ac42012-09-23 08:53:33 -0700129 # Cleanup old tarballs now since we've successfull fetched; only cleanup
130 # the tarballs for our prefix, or unknown ones.
131 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
132 else 'cros-sdk-')
133 for filename in os.listdir(storage_dir):
134 if filename == tarball_name or filename.startswith(ignored_prefix):
135 continue
Brian Harringb938c782012-02-29 15:14:38 -0800136
Brian Harring1790ac42012-09-23 08:53:33 -0700137 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700138 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200139
Brian Harringb938c782012-02-29 15:14:38 -0800140 return tarball_dest
141
142
Brian Harring1790ac42012-09-23 08:53:33 -0700143def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800144 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800145
Brian Harring1790ac42012-09-23 08:53:33 -0700146 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700147 '--chroot', chroot_path,
148 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400149 if nousepkg:
150 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800151
152 try:
153 cros_build_lib.RunCommand(cmd, print_cmd=False)
154 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700155 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800156
157
158def DeleteChroot(chroot_path):
159 """Deletes an existing chroot"""
160 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
161 '--delete']
162 try:
163 cros_build_lib.RunCommand(cmd, print_cmd=False)
164 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700165 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800166
167
Brian Harringae0a5322012-09-15 01:46:51 -0700168def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
169 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800170 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400171 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
172 # The os.ST_NOSUID constant wasn't added until python-3.2.
173 if st.f_flag & 0x2:
174 cros_build_lib.Die('chroot cannot be in a nosuid mount')
175
Brian Harringae0a5322012-09-15 01:46:51 -0700176 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800177 if chrome_root:
178 cmd.extend(['--chrome_root', chrome_root])
179 if chrome_root_mount:
180 cmd.extend(['--chrome_root_mount', chrome_root_mount])
181 if len(additional_args) > 0:
182 cmd.append('--')
183 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700184
185 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
186 # If we were in interactive mode, ignore the exit code; it'll be whatever
187 # they last ran w/in the chroot and won't matter to us one way or another.
188 # Note this does allow chroot entrance to fail and be ignored during
189 # interactive; this is however a rare case and the user will immediately
190 # see it (nor will they be checking the exit code manually).
191 if ret.returncode != 0 and additional_args:
192 raise SystemExit('Running %r failed with exit code %i'
193 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800194
195
David James56e6c2c2012-10-24 23:54:41 -0700196def _SudoCommand():
197 """Get the 'sudo' command, along with all needed environment variables."""
198
David James5a73b4d2013-03-07 10:23:40 -0800199 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
200 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700201 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800202 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700203 value = os.environ.get(key)
204 if value is not None:
205 cmd += ['%s=%s' % (key, value)]
206
207 # Pass in the path to the depot_tools so that users can access them from
208 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400209 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500210
David James56e6c2c2012-10-24 23:54:41 -0700211 return cmd
212
213
Josh Triplett472a4182013-03-08 11:48:57 -0800214def _ReportMissing(missing):
215 """Report missing utilities, then exit.
216
217 Args:
218 missing: List of missing utilities, as returned by
219 osutils.FindMissingBinaries. If non-empty, will not return.
220 """
221
222 if missing:
223 raise SystemExit(
224 'The tool(s) %s were not found.\n'
225 'Please install the appropriate package in your host.\n'
226 'Example(ubuntu):\n'
227 ' sudo apt-get install <packagename>'
228 % ', '.join(missing))
229
230
231def _ProxySimSetup(options):
232 """Set up proxy simulator, and return only in the child environment.
233
234 TODO: Ideally, this should support multiple concurrent invocations of
235 cros_sdk --proxy-sim; currently, such invocations will conflict with each
236 other due to the veth device names and IP addresses. Either this code would
237 need to generate fresh, unused names for all of these before forking, or it
238 would need to support multiple concurrent cros_sdk invocations sharing one
239 proxy and allowing it to exit when unused (without counting on any local
240 service-management infrastructure on the host).
241 """
242
243 may_need_mpm = False
244 apache_bin = osutils.Which('apache2')
245 if apache_bin is None:
246 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
247 if apache_bin is None:
248 _ReportMissing(('apache2',))
249 else:
250 may_need_mpm = True
251
252 # Module names and .so names included for ease of grepping.
253 apache_modules = [('proxy_module', 'mod_proxy.so'),
254 ('proxy_connect_module', 'mod_proxy_connect.so'),
255 ('proxy_http_module', 'mod_proxy_http.so'),
256 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
257
258 # Find the apache module directory, and make sure it has the modules we need.
259 module_dirs = {}
260 for g in PROXY_APACHE_MODULE_GLOBS:
261 for mod, so in apache_modules:
262 for f in glob.glob(os.path.join(g, so)):
263 module_dirs.setdefault(os.path.dirname(f), []).append(so)
264 for apache_module_path, modules_found in module_dirs.iteritems():
265 if len(modules_found) == len(apache_modules):
266 break
267 else:
268 # Appease cros lint, which doesn't understand that this else block will not
269 # fall through to the subsequent code which relies on apache_module_path.
270 apache_module_path = None
271 raise SystemExit(
272 'Could not find apache module path containing all required modules: %s'
273 % ', '.join(so for mod, so in apache_modules))
274
275 def check_add_module(name):
276 so = 'mod_%s.so' % name
277 if os.access(os.path.join(apache_module_path, so), os.F_OK):
278 mod = '%s_module' % name
279 apache_modules.append((mod, so))
280 return True
281 return False
282
283 check_add_module('authz_core')
284 if may_need_mpm:
285 for mpm in PROXY_APACHE_MPMS:
286 if check_add_module('mpm_%s' % mpm):
287 break
288
289 veth_host = '%s-host' % PROXY_VETH_PREFIX
290 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
291
292 # Set up pipes from parent to child and vice versa.
293 # The child writes a byte to the parent after calling unshare, so that the
294 # parent can then assign the guest end of the veth interface to the child's
295 # new network namespace. The parent then writes a byte to the child after
296 # assigning the guest interface, so that the child can then configure that
297 # interface. In both cases, if we get back an EOF when reading from the
298 # pipe, we assume the other end exited with an error message, so just exit.
299 parent_readfd, child_writefd = os.pipe()
300 child_readfd, parent_writefd = os.pipe()
301 SUCCESS_FLAG = '+'
302
303 pid = os.fork()
304 if not pid:
305 os.close(parent_readfd)
306 os.close(parent_writefd)
307
308 namespaces.Unshare(namespaces.CLONE_NEWNET)
309 os.write(child_writefd, SUCCESS_FLAG)
310 os.close(child_writefd)
311 if os.read(child_readfd, 1) != SUCCESS_FLAG:
312 # Parent failed; it will have already have outputted an error message.
313 sys.exit(1)
314 os.close(child_readfd)
315
316 # Set up child side of the network.
317 commands = (
318 ('ip', 'address', 'add',
319 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
320 'dev', veth_guest),
321 ('ip', 'link', 'set', veth_guest, 'up'),
322 )
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 = [
363 '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)),
371 ] + [
372 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
373 for (mod, so) in apache_modules
374 ]
375 commands = (
376 ('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)),
385 )
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 Frysinger80dfce92014-04-21 10:58:53 -0400401 _ExitAsStatus(os.waitpid(pid, 0)[1])
402
403
404def _ExitAsStatus(status):
405 """Exit the same way as |status|.
406
407 If the status field says it was killed by a signal, then we'll do that to
408 ourselves. Otherwise we'll exit with the exit code.
409
410 See http://www.cons.org/cracauer/sigint.html for more details.
411
412 Args:
413 status: A status as returned by os.wait type funcs.
414 """
415 sig_status = status & 0xff
416 exit_status = (status >> 8) & 0xff
417
418 if sig_status:
419 # Kill ourselves with the same signal.
420 pid = os.getpid()
421 os.kill(pid, sig_status)
422 time.sleep(0.1)
423
424 # Still here? Maybe the signal was masked.
425 signal.signal(sig_status, signal.SIG_DFL)
426 os.kill(pid, sig_status)
427 time.sleep(0.1)
428
429 # Still here? Just exit.
430 exit_status = 127
431
432 # Exit with the code we want.
433 sys.exit(exit_status)
434
435
436def _ReapChildren(pid):
437 """Reap all children that get reparented to us until we see |pid| exit.
438
439 Args:
440 pid: The main child to watch for.
441
442 Returns:
443 The wait status of the |pid| child.
444 """
445 pid_status = 0
446
447 while True:
448 try:
449 (wpid, status) = os.wait()
450 if pid == wpid:
451 # Save the status of our main child so we can exit with it below.
452 pid_status = status
453 except OSError as e:
454 if e.errno == errno.ECHILD:
455 break
456 else:
457 raise
458
459 return pid_status
460
461
462def _CreatePidNamespace():
463 """Start a new pid namespace
464
465 This will launch all the right manager processes. The child that returns
466 will be isolated in a new pid namespace.
467
468 If functionality is not available, then it will return w/out doing anything.
469
470 Returns:
471 The last pid outside of the namespace.
472 """
473 first_pid = os.getpid()
474
475 try:
476 # First create the namespace.
477 namespaces.Unshare(namespaces.CLONE_NEWPID)
478 except OSError as e:
479 if e.errno == errno.EINVAL:
480 # For older kernels, or the functionality is disabled in the config,
481 # return silently. We don't want to hard require this stuff.
482 return first_pid
483 else:
484 # For all other errors, abort. They shouldn't happen.
485 raise
486
487 # Now that we're in the new pid namespace, fork. The parent is the master
488 # of it in the original namespace, so it only monitors the child inside it.
489 # It is only allowed to fork once too.
490 pid = os.fork()
491 if pid:
492 # Reap the children as the parent of the new namespace.
493 _ExitAsStatus(_ReapChildren(pid))
494 else:
495 # The child needs its own proc mount as it'll be different.
496 osutils.Mount('proc', '/proc', 'proc',
497 osutils.MS_NOSUID | osutils.MS_NODEV | osutils.MS_NOEXEC |
498 osutils.MS_RELATIME)
499
500 pid = os.fork()
501 if pid:
502 # Watch all of the children. We need to act as the master inside the
503 # namespace and reap old processes.
504 _ExitAsStatus(_ReapChildren(pid))
505
506 # The grandchild will return and take over the rest of the sdk steps.
507 return first_pid
Josh Triplett472a4182013-03-08 11:48:57 -0800508
509
Mike Frysingera78a56e2012-11-20 06:02:30 -0500510def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700511 """Re-execute cros_sdk as root.
512
513 Also unshare the mount namespace so as to ensure that processes outside
514 the chroot can't mess with our mounts.
515 """
516 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500517 cmd = _SudoCommand() + ['--'] + argv
518 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500519 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400520 # We must set up the cgroups mounts before we enter our own namespace.
521 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800522 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400523 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700524
525
Mike Frysinger34db8692013-11-11 14:54:08 -0500526def _CreateParser(sdk_latest_version, bootstrap_latest_version):
527 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700528 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800529
Brian Harring218e13c2012-10-10 16:21:26 -0700530This script is used for manipulating local chroot environments; creating,
531deleting, downloading, etc. If given --enter (or no args), it defaults
532to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800533
Brian Harring218e13c2012-10-10 16:21:26 -0700534If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700535
Brian Harring218e13c2012-10-10 16:21:26 -0700536 parser = commandline.OptionParser(usage=usage, caching=True)
537
Mike Frysinger34db8692013-11-11 14:54:08 -0500538 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500539 default_chroot = os.path.join(constants.SOURCE_ROOT,
540 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700541 parser.add_option(
542 '--chroot', dest='chroot', default=default_chroot, type='path',
543 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800544
Brian Harring218e13c2012-10-10 16:21:26 -0700545 parser.add_option('--chrome_root', default=None, type='path',
546 help='Mount this chrome root into the SDK chroot')
547 parser.add_option('--chrome_root_mount', default=None, type='path',
548 help='Mount chrome into this path inside SDK chroot')
549 parser.add_option('--nousepkg', action='store_true', default=False,
550 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800551 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700552 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800553 help=('''Use sdk tarball located at this url.
554 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700555 parser.add_option('--sdk-version', default=None,
556 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500557 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700558 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500559
560 # Commands.
561 group = parser.add_option_group('Commands')
562 group.add_option(
563 '--enter', action='store_true', default=False,
564 help='Enter the SDK chroot. Implies --create.')
565 group.add_option(
566 '--create', action='store_true',default=False,
567 help='Create the chroot only if it does not already exist. '
568 'Implies --download.')
569 group.add_option(
570 '--bootstrap', action='store_true', default=False,
571 help='Build everything from scratch, including the sdk. '
572 'Use this only if you need to validate a change '
573 'that affects SDK creation itself (toolchain and '
574 'build are typically the only folk who need this). '
575 'Note this will quite heavily slow down the build. '
576 'This option implies --create --nousepkg.')
577 group.add_option(
578 '-r', '--replace', action='store_true', default=False,
579 help='Replace an existing SDK chroot. Basically an alias '
580 'for --delete --create.')
581 group.add_option(
582 '--delete', action='store_true', default=False,
583 help='Delete the current SDK chroot if it exists.')
584 group.add_option(
585 '--download', action='store_true', default=False,
586 help='Download the sdk.')
587 commands = group
588
Mike Frysinger80dfce92014-04-21 10:58:53 -0400589 # Namespace options.
590 group = parser.add_option_group('Namespaces')
591 group.add_option('--proxy-sim', action='store_true', default=False,
592 help='Simulate a restrictive network requiring an outbound'
593 ' proxy.')
594 group.add_option('--no-ns-pid', dest='ns_pid',
595 default=True, action='store_false',
596 help='Do not create a new PID namespace.')
597
Mike Frysinger34db8692013-11-11 14:54:08 -0500598 # Internal options.
599 group = parser.add_option_group(
600 'Internal Chromium OS Build Team Options',
601 'Caution: these are for meant for the Chromium OS build team only')
602 group.add_option('--buildbot-log-version', default=False, action='store_true',
603 help='Log SDK version for buildbot consumption')
604
605 return (parser, commands)
606
607
608def main(argv):
609 conf = cros_build_lib.LoadKeyValueFile(
610 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
611 ignore_missing=True)
612 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
613 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
614 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700615 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800616
617 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500618 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800619
Brian Harring1790ac42012-09-23 08:53:33 -0700620 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700621 if host != 'x86_64':
622 parser.error(
623 "cros_sdk is currently only supported on x86_64; you're running"
624 " %s. Please find a x86_64 machine." % (host,))
625
Josh Triplett472a4182013-03-08 11:48:57 -0800626 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
627 if options.proxy_sim:
628 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800629
David James471532c2013-01-21 10:23:31 -0800630 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400631 if options.ns_pid:
632 first_pid = _CreatePidNamespace()
633 else:
634 first_pid = None
David James471532c2013-01-21 10:23:31 -0800635
Brian Harring218e13c2012-10-10 16:21:26 -0700636 # Expand out the aliases...
637 if options.replace:
638 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800639
Brian Harring218e13c2012-10-10 16:21:26 -0700640 if options.bootstrap:
641 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800642
Brian Harring218e13c2012-10-10 16:21:26 -0700643 # If a command is not given, default to enter.
644 options.enter |= not any(getattr(options, x.dest)
645 for x in commands.option_list)
646 options.enter |= bool(chroot_command)
647
648 if options.enter and options.delete and not options.create:
649 parser.error("Trying to enter the chroot when --delete "
650 "was specified makes no sense.")
651
652 # Finally, discern if we need to create the chroot.
653 chroot_exists = os.path.exists(options.chroot)
654 if options.create or options.enter:
655 # Only create if it's being wiped, or if it doesn't exist.
656 if not options.delete and chroot_exists:
657 options.create = False
658 else:
659 options.download = True
660
661 # Finally, flip create if necessary.
662 if options.enter:
663 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800664
Brian Harringb938c782012-02-29 15:14:38 -0800665 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700666 sdk_version = (bootstrap_latest_version if options.bootstrap
667 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800668 else:
669 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500670 if options.buildbot_log_version:
671 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800672
Brian Harring1790ac42012-09-23 08:53:33 -0700673 # Based on selections, fetch the tarball.
674 if options.sdk_url:
675 urls = [options.sdk_url]
676 elif options.bootstrap:
677 urls = GetStage3Urls(sdk_version)
678 else:
679 urls = GetArchStageTarballs(sdk_version)
680
Brian Harringb6cf9142012-09-01 20:43:17 -0700681 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800682 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700683 '.%s_lock' % os.path.basename(options.chroot))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400684 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700685 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700686
Josh Triplett472a4182013-03-08 11:48:57 -0800687 if options.proxy_sim:
688 _ProxySimSetup(options)
689
David James56e6c2c2012-10-24 23:54:41 -0700690 if options.delete and os.path.exists(options.chroot):
691 lock.write_lock()
692 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800693
David James56e6c2c2012-10-24 23:54:41 -0700694 sdk_cache = os.path.join(options.cache_dir, 'sdks')
695 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700696 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700697
David James56e6c2c2012-10-24 23:54:41 -0700698 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500699 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700700 if not os.path.exists(src):
701 osutils.SafeMakedirs(target)
702 continue
703 lock.write_lock(
704 "Upgrade to %r needed but chroot is locked; please exit "
705 "all instances so this upgrade can finish." % src)
706 if not os.path.exists(src):
707 # Note that while waiting for the write lock, src may've vanished;
708 # it's a rare race during the upgrade process that's a byproduct
709 # of us avoiding taking a write lock to do the src check. If we
710 # took a write lock for that check, it would effectively limit
711 # all cros_sdk for a chroot to a single instance.
712 osutils.SafeMakedirs(target)
713 elif not os.path.exists(target):
714 # Upgrade occurred, but a reversion, or something whacky
715 # occurred writing to the old location. Wipe and continue.
716 os.rename(src, target)
717 else:
718 # Upgrade occurred once already, but either a reversion or
719 # some before/after separate cros_sdk usage is at play.
720 # Wipe and continue.
721 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700722
David James56e6c2c2012-10-24 23:54:41 -0700723 if options.download:
724 lock.write_lock()
725 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700726
David James56e6c2c2012-10-24 23:54:41 -0700727 if options.create:
728 lock.write_lock()
729 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
730 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700731
David James56e6c2c2012-10-24 23:54:41 -0700732 if options.enter:
733 lock.read_lock()
734 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
735 options.chrome_root_mount, chroot_command)