blob: 84a47118b32c3c5a22b3a74059fb49a8998dcd4c [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 Frysinger80dfce92014-04-21 10:58:53 -04007import errno
Josh Triplett472a4182013-03-08 11:48:57 -08008import glob
Brian Harringb938c782012-02-29 15:14:38 -08009import os
Josh Triplett472a4182013-03-08 11:48:57 -080010import pwd
Mike Frysinger80dfce92014-04-21 10:58:53 -040011import signal
David James56e6c2c2012-10-24 23:54:41 -070012import sys
Mike Frysinger80dfce92014-04-21 10:58:53 -040013import time
Brian Harringb938c782012-02-29 15:14:38 -080014import urlparse
15
Don Garrett88b8d782014-05-13 17:30:55 -070016from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080017from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070018from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080019from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080020from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080021from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070022from chromite.lib import osutils
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
Zdenek Behanfd0efe42012-04-13 04:36:40 +020096 print 'Attempting download: %s' % url
David Jamesc93e6a4d2014-01-13 11:37:36 -080097 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -070098 ['-I', url], redirect_stdout=True, redirect_stderr=True,
99 print_cmd=False)
100 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
106 elif header.lower().startswith("content-length:"):
107 content_length = int(header.split(":", 1)[-1].strip())
108 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
Brian Harring1790ac42012-09-23 08:53:33 -0700136 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'
272 % ', '.join(so for mod, so in apache_modules))
273
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 = (
317 ('ip', 'address', 'add',
318 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
319 'dev', veth_guest),
320 ('ip', 'link', 'set', veth_guest, 'up'),
321 )
322 try:
323 for cmd in commands:
324 cros_build_lib.RunCommand(cmd, print_cmd=False)
325 except cros_build_lib.RunCommandError:
326 raise SystemExit('Running %r failed!' % (cmd,))
327
328 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
329 for proto in ('http', 'https', 'ftp'):
330 os.environ[proto + '_proxy'] = proxy_url
331 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
332 os.environ.pop(v, None)
333 return
334
335 os.close(child_readfd)
336 os.close(child_writefd)
337
338 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
339 # Child failed; it will have already have outputted an error message.
340 sys.exit(1)
341 os.close(parent_readfd)
342
343 # Set up parent side of the network.
344 uid = int(os.environ.get('SUDO_UID', '0'))
345 gid = int(os.environ.get('SUDO_GID', '0'))
346 if uid == 0 or gid == 0:
347 for username in PROXY_APACHE_FALLBACK_USERS:
348 try:
349 pwnam = pwd.getpwnam(username)
350 uid, gid = pwnam.pw_uid, pwnam.pw_gid
351 break
352 except KeyError:
353 continue
354 if uid == 0 or gid == 0:
355 raise SystemExit('Could not find a non-root user to run Apache as')
356
357 chroot_parent, chroot_base = os.path.split(options.chroot)
358 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
359 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
360
361 apache_directives = [
362 'User #%u' % uid,
363 'Group #%u' % gid,
364 'PidFile %s' % pid_file,
365 'ErrorLog %s' % log_file,
366 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
367 'ServerName %s' % PROXY_HOST_IP,
368 'ProxyRequests On',
369 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
370 ] + [
371 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
372 for (mod, so) in apache_modules
373 ]
374 commands = (
375 ('ip', 'link', 'add', 'name', veth_host,
376 'type', 'veth', 'peer', 'name', veth_guest),
377 ('ip', 'address', 'add',
378 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
379 'dev', veth_host),
380 ('ip', 'link', 'set', veth_host, 'up'),
381 [apache_bin, '-f', '/dev/null']
382 + [arg for d in apache_directives for arg in ('-C', d)],
383 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
384 )
385 cmd = None # Make cros lint happy.
386 try:
387 for cmd in commands:
388 cros_build_lib.RunCommand(cmd, print_cmd=False)
389 except cros_build_lib.RunCommandError:
390 # Clean up existing interfaces, if any.
391 cmd_cleanup = ('ip', 'link', 'del', veth_host)
392 try:
393 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
394 except cros_build_lib.RunCommandError:
395 cros_build_lib.Error('running %r failed', cmd_cleanup)
396 raise SystemExit('Running %r failed!' % (cmd,))
397 os.write(parent_writefd, SUCCESS_FLAG)
398 os.close(parent_writefd)
399
Mike Frysinger80dfce92014-04-21 10:58:53 -0400400 _ExitAsStatus(os.waitpid(pid, 0)[1])
401
402
403def _ExitAsStatus(status):
404 """Exit the same way as |status|.
405
406 If the status field says it was killed by a signal, then we'll do that to
407 ourselves. Otherwise we'll exit with the exit code.
408
409 See http://www.cons.org/cracauer/sigint.html for more details.
410
411 Args:
412 status: A status as returned by os.wait type funcs.
413 """
414 sig_status = status & 0xff
415 exit_status = (status >> 8) & 0xff
416
417 if sig_status:
418 # Kill ourselves with the same signal.
419 pid = os.getpid()
420 os.kill(pid, sig_status)
421 time.sleep(0.1)
422
423 # Still here? Maybe the signal was masked.
424 signal.signal(sig_status, signal.SIG_DFL)
425 os.kill(pid, sig_status)
426 time.sleep(0.1)
427
428 # Still here? Just exit.
429 exit_status = 127
430
431 # Exit with the code we want.
432 sys.exit(exit_status)
433
434
435def _ReapChildren(pid):
436 """Reap all children that get reparented to us until we see |pid| exit.
437
438 Args:
439 pid: The main child to watch for.
440
441 Returns:
442 The wait status of the |pid| child.
443 """
444 pid_status = 0
445
446 while True:
447 try:
448 (wpid, status) = os.wait()
449 if pid == wpid:
450 # Save the status of our main child so we can exit with it below.
451 pid_status = status
452 except OSError as e:
453 if e.errno == errno.ECHILD:
454 break
455 else:
456 raise
457
458 return pid_status
459
460
461def _CreatePidNamespace():
462 """Start a new pid namespace
463
464 This will launch all the right manager processes. The child that returns
465 will be isolated in a new pid namespace.
466
467 If functionality is not available, then it will return w/out doing anything.
468
469 Returns:
470 The last pid outside of the namespace.
471 """
472 first_pid = os.getpid()
473
474 try:
475 # First create the namespace.
476 namespaces.Unshare(namespaces.CLONE_NEWPID)
477 except OSError as e:
478 if e.errno == errno.EINVAL:
479 # For older kernels, or the functionality is disabled in the config,
480 # return silently. We don't want to hard require this stuff.
481 return first_pid
482 else:
483 # For all other errors, abort. They shouldn't happen.
484 raise
485
486 # Now that we're in the new pid namespace, fork. The parent is the master
487 # of it in the original namespace, so it only monitors the child inside it.
488 # It is only allowed to fork once too.
489 pid = os.fork()
490 if pid:
491 # Reap the children as the parent of the new namespace.
492 _ExitAsStatus(_ReapChildren(pid))
493 else:
494 # The child needs its own proc mount as it'll be different.
495 osutils.Mount('proc', '/proc', 'proc',
496 osutils.MS_NOSUID | osutils.MS_NODEV | osutils.MS_NOEXEC |
497 osutils.MS_RELATIME)
498
499 pid = os.fork()
500 if pid:
501 # Watch all of the children. We need to act as the master inside the
502 # namespace and reap old processes.
503 _ExitAsStatus(_ReapChildren(pid))
504
505 # The grandchild will return and take over the rest of the sdk steps.
506 return first_pid
Josh Triplett472a4182013-03-08 11:48:57 -0800507
508
Mike Frysingera78a56e2012-11-20 06:02:30 -0500509def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700510 """Re-execute cros_sdk as root.
511
512 Also unshare the mount namespace so as to ensure that processes outside
513 the chroot can't mess with our mounts.
514 """
515 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500516 cmd = _SudoCommand() + ['--'] + argv
517 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500518 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400519 # We must set up the cgroups mounts before we enter our own namespace.
520 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800521 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400522 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700523
524
Mike Frysinger34db8692013-11-11 14:54:08 -0500525def _CreateParser(sdk_latest_version, bootstrap_latest_version):
526 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700527 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800528
Brian Harring218e13c2012-10-10 16:21:26 -0700529This script is used for manipulating local chroot environments; creating,
530deleting, downloading, etc. If given --enter (or no args), it defaults
531to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800532
Brian Harring218e13c2012-10-10 16:21:26 -0700533If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700534
Brian Harring218e13c2012-10-10 16:21:26 -0700535 parser = commandline.OptionParser(usage=usage, caching=True)
536
Mike Frysinger34db8692013-11-11 14:54:08 -0500537 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500538 default_chroot = os.path.join(constants.SOURCE_ROOT,
539 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700540 parser.add_option(
541 '--chroot', dest='chroot', default=default_chroot, type='path',
542 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800543
Brian Harring218e13c2012-10-10 16:21:26 -0700544 parser.add_option('--chrome_root', default=None, type='path',
545 help='Mount this chrome root into the SDK chroot')
546 parser.add_option('--chrome_root_mount', default=None, type='path',
547 help='Mount chrome into this path inside SDK chroot')
548 parser.add_option('--nousepkg', action='store_true', default=False,
549 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800550 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700551 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800552 help=('''Use sdk tarball located at this url.
553 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700554 parser.add_option('--sdk-version', default=None,
555 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500556 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700557 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500558
559 # Commands.
560 group = parser.add_option_group('Commands')
561 group.add_option(
562 '--enter', action='store_true', default=False,
563 help='Enter the SDK chroot. Implies --create.')
564 group.add_option(
565 '--create', action='store_true',default=False,
566 help='Create the chroot only if it does not already exist. '
567 'Implies --download.')
568 group.add_option(
569 '--bootstrap', action='store_true', default=False,
570 help='Build everything from scratch, including the sdk. '
571 'Use this only if you need to validate a change '
572 'that affects SDK creation itself (toolchain and '
573 'build are typically the only folk who need this). '
574 'Note this will quite heavily slow down the build. '
575 'This option implies --create --nousepkg.')
576 group.add_option(
577 '-r', '--replace', action='store_true', default=False,
578 help='Replace an existing SDK chroot. Basically an alias '
579 'for --delete --create.')
580 group.add_option(
581 '--delete', action='store_true', default=False,
582 help='Delete the current SDK chroot if it exists.')
583 group.add_option(
584 '--download', action='store_true', default=False,
585 help='Download the sdk.')
586 commands = group
587
Mike Frysinger80dfce92014-04-21 10:58:53 -0400588 # Namespace options.
589 group = parser.add_option_group('Namespaces')
590 group.add_option('--proxy-sim', action='store_true', default=False,
591 help='Simulate a restrictive network requiring an outbound'
592 ' proxy.')
Mike Frysinger94b32a12014-09-12 14:28:13 -0400593 # TODO(vapier): Turn off pid ns until ccache issues can be fixed.
594 # http://crbug.com/411984
Mike Frysinger80dfce92014-04-21 10:58:53 -0400595 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger94b32a12014-09-12 14:28:13 -0400596 default=False, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400597 help='Do not create a new PID namespace.')
598
Mike Frysinger34db8692013-11-11 14:54:08 -0500599 # Internal options.
600 group = parser.add_option_group(
601 'Internal Chromium OS Build Team Options',
602 'Caution: these are for meant for the Chromium OS build team only')
603 group.add_option('--buildbot-log-version', default=False, action='store_true',
604 help='Log SDK version for buildbot consumption')
605
606 return (parser, commands)
607
608
609def main(argv):
610 conf = cros_build_lib.LoadKeyValueFile(
611 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
612 ignore_missing=True)
613 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
614 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
615 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700616 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800617
618 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500619 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800620
Brian Harring1790ac42012-09-23 08:53:33 -0700621 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700622 if host != 'x86_64':
623 parser.error(
624 "cros_sdk is currently only supported on x86_64; you're running"
625 " %s. Please find a x86_64 machine." % (host,))
626
Josh Triplett472a4182013-03-08 11:48:57 -0800627 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
628 if options.proxy_sim:
629 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800630
David James471532c2013-01-21 10:23:31 -0800631 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400632 if options.ns_pid:
633 first_pid = _CreatePidNamespace()
634 else:
635 first_pid = None
David James471532c2013-01-21 10:23:31 -0800636
Brian Harring218e13c2012-10-10 16:21:26 -0700637 # Expand out the aliases...
638 if options.replace:
639 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800640
Brian Harring218e13c2012-10-10 16:21:26 -0700641 if options.bootstrap:
642 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800643
Brian Harring218e13c2012-10-10 16:21:26 -0700644 # If a command is not given, default to enter.
645 options.enter |= not any(getattr(options, x.dest)
646 for x in commands.option_list)
647 options.enter |= bool(chroot_command)
648
649 if options.enter and options.delete and not options.create:
650 parser.error("Trying to enter the chroot when --delete "
651 "was specified makes no sense.")
652
653 # Finally, discern if we need to create the chroot.
654 chroot_exists = os.path.exists(options.chroot)
655 if options.create or options.enter:
656 # Only create if it's being wiped, or if it doesn't exist.
657 if not options.delete and chroot_exists:
658 options.create = False
659 else:
660 options.download = True
661
662 # Finally, flip create if necessary.
663 if options.enter:
664 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800665
Brian Harringb938c782012-02-29 15:14:38 -0800666 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700667 sdk_version = (bootstrap_latest_version if options.bootstrap
668 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800669 else:
670 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500671 if options.buildbot_log_version:
672 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800673
Brian Harring1790ac42012-09-23 08:53:33 -0700674 # Based on selections, fetch the tarball.
675 if options.sdk_url:
676 urls = [options.sdk_url]
677 elif options.bootstrap:
678 urls = GetStage3Urls(sdk_version)
679 else:
680 urls = GetArchStageTarballs(sdk_version)
681
Brian Harringb6cf9142012-09-01 20:43:17 -0700682 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800683 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700684 '.%s_lock' % os.path.basename(options.chroot))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400685 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700686 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700687
Josh Triplett472a4182013-03-08 11:48:57 -0800688 if options.proxy_sim:
689 _ProxySimSetup(options)
690
David James56e6c2c2012-10-24 23:54:41 -0700691 if options.delete and os.path.exists(options.chroot):
692 lock.write_lock()
693 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800694
David James56e6c2c2012-10-24 23:54:41 -0700695 sdk_cache = os.path.join(options.cache_dir, 'sdks')
696 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700697 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700698
David James56e6c2c2012-10-24 23:54:41 -0700699 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500700 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700701 if not os.path.exists(src):
702 osutils.SafeMakedirs(target)
703 continue
704 lock.write_lock(
705 "Upgrade to %r needed but chroot is locked; please exit "
706 "all instances so this upgrade can finish." % src)
707 if not os.path.exists(src):
708 # Note that while waiting for the write lock, src may've vanished;
709 # it's a rare race during the upgrade process that's a byproduct
710 # of us avoiding taking a write lock to do the src check. If we
711 # took a write lock for that check, it would effectively limit
712 # all cros_sdk for a chroot to a single instance.
713 osutils.SafeMakedirs(target)
714 elif not os.path.exists(target):
715 # Upgrade occurred, but a reversion, or something whacky
716 # occurred writing to the old location. Wipe and continue.
717 os.rename(src, target)
718 else:
719 # Upgrade occurred once already, but either a reversion or
720 # some before/after separate cros_sdk usage is at play.
721 # Wipe and continue.
722 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700723
David James56e6c2c2012-10-24 23:54:41 -0700724 if options.download:
725 lock.write_lock()
726 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700727
David James56e6c2c2012-10-24 23:54:41 -0700728 if options.create:
729 lock.write_lock()
730 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
731 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700732
David James56e6c2c2012-10-24 23:54:41 -0700733 if options.enter:
734 lock.read_lock()
735 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
736 options.chrome_root_mount, chroot_command)