blob: 822cef0662447061e9d98836ced8aa732dc355e1 [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
Mike Frysinger80dfce92014-04-21 10:58:53 -04009import errno
Josh Triplett472a4182013-03-08 11:48:57 -080010import glob
Brian Harringb938c782012-02-29 15:14:38 -080011import os
Josh Triplett472a4182013-03-08 11:48:57 -080012import pwd
Mike Frysinger80dfce92014-04-21 10:58:53 -040013import signal
David James56e6c2c2012-10-24 23:54:41 -070014import sys
Mike Frysinger80dfce92014-04-21 10:58:53 -040015import time
Brian Harringb938c782012-02-29 15:14:38 -080016import urlparse
17
Don Garrett88b8d782014-05-13 17:30:55 -070018from chromite.cbuildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080019from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070020from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080021from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080022from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080023from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070024from chromite.lib import osutils
David Jamesc93e6a4d2014-01-13 11:37:36 -080025from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050026from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080027
28cros_build_lib.STRICT_SUDO = True
29
30
Zdenek Behanaa52cea2012-05-30 01:31:11 +020031COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020032
Brian Harringb938c782012-02-29 15:14:38 -080033# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050034MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
35 'src/scripts/sdk_lib/make_chroot.sh')]
36ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
37 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080038
Josh Triplett472a4182013-03-08 11:48:57 -080039# Proxy simulator configuration.
40PROXY_HOST_IP = '192.168.240.1'
41PROXY_PORT = 8080
42PROXY_GUEST_IP = '192.168.240.2'
43PROXY_NETMASK = 30
44PROXY_VETH_PREFIX = 'veth'
45PROXY_CONNECT_PORTS = (80, 443, 9418)
46PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
47PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
48PROXY_APACHE_FALLBACK_PATH = ':'.join(
49 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
50)
51PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
52
Josh Triplett9a495f62013-03-15 18:06:55 -070053# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080054NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080055
Josh Triplett472a4182013-03-08 11:48:57 -080056# Tools needed for --proxy-sim only.
57PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080058
Mike Frysingercc838832014-05-24 13:10:30 -040059
Brian Harring1790ac42012-09-23 08:53:33 -070060def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080061 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070062 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050063 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
64 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070065 for compressor in COMPRESSION_PREFERENCE]
66
67
68def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050069 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070070 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080071
72
Brian Harringae0a5322012-09-15 01:46:51 -070073def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050074 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020075
76 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050077 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020078 urls: List of URLs to try to download. Download will stop on first success.
79
80 Returns:
81 Full path to the downloaded file
82 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020083
Brian Harring1790ac42012-09-23 08:53:33 -070084 # Note we track content length ourselves since certain versions of curl
85 # fail if asked to resume a complete file.
86 # pylint: disable=C0301,W0631
87 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020088 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070089 # http://www.logilab.org/ticket/8766
90 # pylint: disable=E1101
91 parsed = urlparse.urlparse(url)
92 tarball_name = os.path.basename(parsed.path)
93 if parsed.scheme in ('', 'file'):
94 if os.path.exists(parsed.path):
95 return parsed.path
96 continue
97 content_length = 0
Mike Frysinger383367e2014-09-16 15:06:17 -040098 print('Attempting download: %s' % url)
David Jamesc93e6a4d2014-01-13 11:37:36 -080099 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700100 ['-I', url], redirect_stdout=True, redirect_stderr=True,
101 print_cmd=False)
102 successful = False
103 for header in result.output.splitlines():
104 # We must walk the output to find the string '200 OK' for use cases where
105 # a proxy is involved and may have pushed down the actual header.
106 if header.find('200 OK') != -1:
107 successful = True
108 elif header.lower().startswith("content-length:"):
109 content_length = int(header.split(":", 1)[-1].strip())
110 if successful:
111 break
112 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200113 break
114 else:
115 raise Exception('No valid URLs found!')
116
Brian Harringae0a5322012-09-15 01:46:51 -0700117 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700118 current_size = 0
119 if os.path.exists(tarball_dest):
120 current_size = os.path.getsize(tarball_dest)
121 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700122 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700123 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100124
Brian Harring1790ac42012-09-23 08:53:33 -0700125 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800126 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700127 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
128 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800129
Brian Harring1790ac42012-09-23 08:53:33 -0700130 # Cleanup old tarballs now since we've successfull fetched; only cleanup
131 # the tarballs for our prefix, or unknown ones.
132 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
133 else 'cros-sdk-')
134 for filename in os.listdir(storage_dir):
135 if filename == tarball_name or filename.startswith(ignored_prefix):
136 continue
Brian Harringb938c782012-02-29 15:14:38 -0800137
Mike Frysinger383367e2014-09-16 15:06:17 -0400138 print('Cleaning up old tarball: %s' % (filename,))
David James56e6c2c2012-10-24 23:54:41 -0700139 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200140
Brian Harringb938c782012-02-29 15:14:38 -0800141 return tarball_dest
142
143
Brian Harring1790ac42012-09-23 08:53:33 -0700144def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800145 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800146
Brian Harring1790ac42012-09-23 08:53:33 -0700147 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700148 '--chroot', chroot_path,
149 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400150 if nousepkg:
151 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800152
153 try:
154 cros_build_lib.RunCommand(cmd, print_cmd=False)
155 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700156 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800157
158
159def DeleteChroot(chroot_path):
160 """Deletes an existing chroot"""
161 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
162 '--delete']
163 try:
164 cros_build_lib.RunCommand(cmd, print_cmd=False)
165 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700166 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800167
168
Brian Harringae0a5322012-09-15 01:46:51 -0700169def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
170 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800171 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400172 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
173 # The os.ST_NOSUID constant wasn't added until python-3.2.
174 if st.f_flag & 0x2:
175 cros_build_lib.Die('chroot cannot be in a nosuid mount')
176
Brian Harringae0a5322012-09-15 01:46:51 -0700177 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800178 if chrome_root:
179 cmd.extend(['--chrome_root', chrome_root])
180 if chrome_root_mount:
181 cmd.extend(['--chrome_root_mount', chrome_root_mount])
182 if len(additional_args) > 0:
183 cmd.append('--')
184 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700185
186 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
187 # If we were in interactive mode, ignore the exit code; it'll be whatever
188 # they last ran w/in the chroot and won't matter to us one way or another.
189 # Note this does allow chroot entrance to fail and be ignored during
190 # interactive; this is however a rare case and the user will immediately
191 # see it (nor will they be checking the exit code manually).
192 if ret.returncode != 0 and additional_args:
193 raise SystemExit('Running %r failed with exit code %i'
194 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800195
196
David James56e6c2c2012-10-24 23:54:41 -0700197def _SudoCommand():
198 """Get the 'sudo' command, along with all needed environment variables."""
199
David James5a73b4d2013-03-07 10:23:40 -0800200 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
201 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700202 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800203 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700204 value = os.environ.get(key)
205 if value is not None:
206 cmd += ['%s=%s' % (key, value)]
207
208 # Pass in the path to the depot_tools so that users can access them from
209 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400210 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500211
David James56e6c2c2012-10-24 23:54:41 -0700212 return cmd
213
214
Josh Triplett472a4182013-03-08 11:48:57 -0800215def _ReportMissing(missing):
216 """Report missing utilities, then exit.
217
218 Args:
219 missing: List of missing utilities, as returned by
220 osutils.FindMissingBinaries. If non-empty, will not return.
221 """
222
223 if missing:
224 raise SystemExit(
225 'The tool(s) %s were not found.\n'
226 'Please install the appropriate package in your host.\n'
227 'Example(ubuntu):\n'
228 ' sudo apt-get install <packagename>'
229 % ', '.join(missing))
230
231
232def _ProxySimSetup(options):
233 """Set up proxy simulator, and return only in the child environment.
234
235 TODO: Ideally, this should support multiple concurrent invocations of
236 cros_sdk --proxy-sim; currently, such invocations will conflict with each
237 other due to the veth device names and IP addresses. Either this code would
238 need to generate fresh, unused names for all of these before forking, or it
239 would need to support multiple concurrent cros_sdk invocations sharing one
240 proxy and allowing it to exit when unused (without counting on any local
241 service-management infrastructure on the host).
242 """
243
244 may_need_mpm = False
245 apache_bin = osutils.Which('apache2')
246 if apache_bin is None:
247 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
248 if apache_bin is None:
249 _ReportMissing(('apache2',))
250 else:
251 may_need_mpm = True
252
253 # Module names and .so names included for ease of grepping.
254 apache_modules = [('proxy_module', 'mod_proxy.so'),
255 ('proxy_connect_module', 'mod_proxy_connect.so'),
256 ('proxy_http_module', 'mod_proxy_http.so'),
257 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
258
259 # Find the apache module directory, and make sure it has the modules we need.
260 module_dirs = {}
261 for g in PROXY_APACHE_MODULE_GLOBS:
262 for mod, so in apache_modules:
263 for f in glob.glob(os.path.join(g, so)):
264 module_dirs.setdefault(os.path.dirname(f), []).append(so)
265 for apache_module_path, modules_found in module_dirs.iteritems():
266 if len(modules_found) == len(apache_modules):
267 break
268 else:
269 # Appease cros lint, which doesn't understand that this else block will not
270 # fall through to the subsequent code which relies on apache_module_path.
271 apache_module_path = None
272 raise SystemExit(
273 'Could not find apache module path containing all required modules: %s'
274 % ', '.join(so for mod, so in apache_modules))
275
276 def check_add_module(name):
277 so = 'mod_%s.so' % name
278 if os.access(os.path.join(apache_module_path, so), os.F_OK):
279 mod = '%s_module' % name
280 apache_modules.append((mod, so))
281 return True
282 return False
283
284 check_add_module('authz_core')
285 if may_need_mpm:
286 for mpm in PROXY_APACHE_MPMS:
287 if check_add_module('mpm_%s' % mpm):
288 break
289
290 veth_host = '%s-host' % PROXY_VETH_PREFIX
291 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
292
293 # Set up pipes from parent to child and vice versa.
294 # The child writes a byte to the parent after calling unshare, so that the
295 # parent can then assign the guest end of the veth interface to the child's
296 # new network namespace. The parent then writes a byte to the child after
297 # assigning the guest interface, so that the child can then configure that
298 # interface. In both cases, if we get back an EOF when reading from the
299 # pipe, we assume the other end exited with an error message, so just exit.
300 parent_readfd, child_writefd = os.pipe()
301 child_readfd, parent_writefd = os.pipe()
302 SUCCESS_FLAG = '+'
303
304 pid = os.fork()
305 if not pid:
306 os.close(parent_readfd)
307 os.close(parent_writefd)
308
309 namespaces.Unshare(namespaces.CLONE_NEWNET)
310 os.write(child_writefd, SUCCESS_FLAG)
311 os.close(child_writefd)
312 if os.read(child_readfd, 1) != SUCCESS_FLAG:
313 # Parent failed; it will have already have outputted an error message.
314 sys.exit(1)
315 os.close(child_readfd)
316
317 # Set up child side of the network.
318 commands = (
319 ('ip', 'address', 'add',
320 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
321 'dev', veth_guest),
322 ('ip', 'link', 'set', veth_guest, 'up'),
323 )
324 try:
325 for cmd in commands:
326 cros_build_lib.RunCommand(cmd, print_cmd=False)
327 except cros_build_lib.RunCommandError:
328 raise SystemExit('Running %r failed!' % (cmd,))
329
330 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
331 for proto in ('http', 'https', 'ftp'):
332 os.environ[proto + '_proxy'] = proxy_url
333 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
334 os.environ.pop(v, None)
335 return
336
337 os.close(child_readfd)
338 os.close(child_writefd)
339
340 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
341 # Child failed; it will have already have outputted an error message.
342 sys.exit(1)
343 os.close(parent_readfd)
344
345 # Set up parent side of the network.
346 uid = int(os.environ.get('SUDO_UID', '0'))
347 gid = int(os.environ.get('SUDO_GID', '0'))
348 if uid == 0 or gid == 0:
349 for username in PROXY_APACHE_FALLBACK_USERS:
350 try:
351 pwnam = pwd.getpwnam(username)
352 uid, gid = pwnam.pw_uid, pwnam.pw_gid
353 break
354 except KeyError:
355 continue
356 if uid == 0 or gid == 0:
357 raise SystemExit('Could not find a non-root user to run Apache as')
358
359 chroot_parent, chroot_base = os.path.split(options.chroot)
360 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
361 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
362
363 apache_directives = [
364 'User #%u' % uid,
365 'Group #%u' % gid,
366 'PidFile %s' % pid_file,
367 'ErrorLog %s' % log_file,
368 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
369 'ServerName %s' % PROXY_HOST_IP,
370 'ProxyRequests On',
371 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
372 ] + [
373 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
374 for (mod, so) in apache_modules
375 ]
376 commands = (
377 ('ip', 'link', 'add', 'name', veth_host,
378 'type', 'veth', 'peer', 'name', veth_guest),
379 ('ip', 'address', 'add',
380 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
381 'dev', veth_host),
382 ('ip', 'link', 'set', veth_host, 'up'),
383 [apache_bin, '-f', '/dev/null']
384 + [arg for d in apache_directives for arg in ('-C', d)],
385 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
386 )
387 cmd = None # Make cros lint happy.
388 try:
389 for cmd in commands:
390 cros_build_lib.RunCommand(cmd, print_cmd=False)
391 except cros_build_lib.RunCommandError:
392 # Clean up existing interfaces, if any.
393 cmd_cleanup = ('ip', 'link', 'del', veth_host)
394 try:
395 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
396 except cros_build_lib.RunCommandError:
397 cros_build_lib.Error('running %r failed', cmd_cleanup)
398 raise SystemExit('Running %r failed!' % (cmd,))
399 os.write(parent_writefd, SUCCESS_FLAG)
400 os.close(parent_writefd)
401
Mike Frysinger80dfce92014-04-21 10:58:53 -0400402 _ExitAsStatus(os.waitpid(pid, 0)[1])
403
404
405def _ExitAsStatus(status):
406 """Exit the same way as |status|.
407
408 If the status field says it was killed by a signal, then we'll do that to
409 ourselves. Otherwise we'll exit with the exit code.
410
411 See http://www.cons.org/cracauer/sigint.html for more details.
412
413 Args:
414 status: A status as returned by os.wait type funcs.
415 """
416 sig_status = status & 0xff
417 exit_status = (status >> 8) & 0xff
418
419 if sig_status:
420 # Kill ourselves with the same signal.
421 pid = os.getpid()
422 os.kill(pid, sig_status)
423 time.sleep(0.1)
424
425 # Still here? Maybe the signal was masked.
426 signal.signal(sig_status, signal.SIG_DFL)
427 os.kill(pid, sig_status)
428 time.sleep(0.1)
429
430 # Still here? Just exit.
431 exit_status = 127
432
433 # Exit with the code we want.
434 sys.exit(exit_status)
435
436
437def _ReapChildren(pid):
438 """Reap all children that get reparented to us until we see |pid| exit.
439
440 Args:
441 pid: The main child to watch for.
442
443 Returns:
444 The wait status of the |pid| child.
445 """
446 pid_status = 0
447
448 while True:
449 try:
450 (wpid, status) = os.wait()
451 if pid == wpid:
452 # Save the status of our main child so we can exit with it below.
453 pid_status = status
454 except OSError as e:
455 if e.errno == errno.ECHILD:
456 break
457 else:
458 raise
459
460 return pid_status
461
462
463def _CreatePidNamespace():
464 """Start a new pid namespace
465
466 This will launch all the right manager processes. The child that returns
467 will be isolated in a new pid namespace.
468
469 If functionality is not available, then it will return w/out doing anything.
470
471 Returns:
472 The last pid outside of the namespace.
473 """
474 first_pid = os.getpid()
475
476 try:
477 # First create the namespace.
478 namespaces.Unshare(namespaces.CLONE_NEWPID)
479 except OSError as e:
480 if e.errno == errno.EINVAL:
481 # For older kernels, or the functionality is disabled in the config,
482 # return silently. We don't want to hard require this stuff.
483 return first_pid
484 else:
485 # For all other errors, abort. They shouldn't happen.
486 raise
487
488 # Now that we're in the new pid namespace, fork. The parent is the master
489 # of it in the original namespace, so it only monitors the child inside it.
490 # It is only allowed to fork once too.
491 pid = os.fork()
492 if pid:
493 # Reap the children as the parent of the new namespace.
494 _ExitAsStatus(_ReapChildren(pid))
495 else:
496 # The child needs its own proc mount as it'll be different.
497 osutils.Mount('proc', '/proc', 'proc',
498 osutils.MS_NOSUID | osutils.MS_NODEV | osutils.MS_NOEXEC |
499 osutils.MS_RELATIME)
500
501 pid = os.fork()
502 if pid:
503 # Watch all of the children. We need to act as the master inside the
504 # namespace and reap old processes.
505 _ExitAsStatus(_ReapChildren(pid))
506
507 # The grandchild will return and take over the rest of the sdk steps.
508 return first_pid
Josh Triplett472a4182013-03-08 11:48:57 -0800509
510
Mike Frysingera78a56e2012-11-20 06:02:30 -0500511def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700512 """Re-execute cros_sdk as root.
513
514 Also unshare the mount namespace so as to ensure that processes outside
515 the chroot can't mess with our mounts.
516 """
517 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500518 cmd = _SudoCommand() + ['--'] + argv
519 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500520 else:
Mike Frysinger80dfce92014-04-21 10:58:53 -0400521 # We must set up the cgroups mounts before we enter our own namespace.
522 # This way it is a shared resource in the root mount namespace.
Josh Triplette759b232013-03-08 13:03:43 -0800523 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400524 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700525
526
Mike Frysinger34db8692013-11-11 14:54:08 -0500527def _CreateParser(sdk_latest_version, bootstrap_latest_version):
528 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700529 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800530
Brian Harring218e13c2012-10-10 16:21:26 -0700531This script is used for manipulating local chroot environments; creating,
532deleting, downloading, etc. If given --enter (or no args), it defaults
533to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800534
Brian Harring218e13c2012-10-10 16:21:26 -0700535If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700536
Brian Harring218e13c2012-10-10 16:21:26 -0700537 parser = commandline.OptionParser(usage=usage, caching=True)
538
Mike Frysinger34db8692013-11-11 14:54:08 -0500539 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500540 default_chroot = os.path.join(constants.SOURCE_ROOT,
541 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700542 parser.add_option(
543 '--chroot', dest='chroot', default=default_chroot, type='path',
544 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800545
Brian Harring218e13c2012-10-10 16:21:26 -0700546 parser.add_option('--chrome_root', default=None, type='path',
547 help='Mount this chrome root into the SDK chroot')
548 parser.add_option('--chrome_root_mount', default=None, type='path',
549 help='Mount chrome into this path inside SDK chroot')
550 parser.add_option('--nousepkg', action='store_true', default=False,
551 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800552 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700553 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800554 help=('''Use sdk tarball located at this url.
555 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700556 parser.add_option('--sdk-version', default=None,
557 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500558 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700559 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500560
561 # Commands.
562 group = parser.add_option_group('Commands')
563 group.add_option(
564 '--enter', action='store_true', default=False,
565 help='Enter the SDK chroot. Implies --create.')
566 group.add_option(
567 '--create', action='store_true',default=False,
568 help='Create the chroot only if it does not already exist. '
569 'Implies --download.')
570 group.add_option(
571 '--bootstrap', action='store_true', default=False,
572 help='Build everything from scratch, including the sdk. '
573 'Use this only if you need to validate a change '
574 'that affects SDK creation itself (toolchain and '
575 'build are typically the only folk who need this). '
576 'Note this will quite heavily slow down the build. '
577 'This option implies --create --nousepkg.')
578 group.add_option(
579 '-r', '--replace', action='store_true', default=False,
580 help='Replace an existing SDK chroot. Basically an alias '
581 'for --delete --create.')
582 group.add_option(
583 '--delete', action='store_true', default=False,
584 help='Delete the current SDK chroot if it exists.')
585 group.add_option(
586 '--download', action='store_true', default=False,
587 help='Download the sdk.')
588 commands = group
589
Mike Frysinger80dfce92014-04-21 10:58:53 -0400590 # Namespace options.
591 group = parser.add_option_group('Namespaces')
592 group.add_option('--proxy-sim', action='store_true', default=False,
593 help='Simulate a restrictive network requiring an outbound'
594 ' proxy.')
Mike Frysinger94b32a12014-09-12 14:28:13 -0400595 # TODO(vapier): Turn off pid ns until ccache issues can be fixed.
596 # http://crbug.com/411984
Mike Frysinger80dfce92014-04-21 10:58:53 -0400597 group.add_option('--no-ns-pid', dest='ns_pid',
Mike Frysinger94b32a12014-09-12 14:28:13 -0400598 default=False, action='store_false',
Mike Frysinger80dfce92014-04-21 10:58:53 -0400599 help='Do not create a new PID namespace.')
600
Mike Frysinger34db8692013-11-11 14:54:08 -0500601 # Internal options.
602 group = parser.add_option_group(
603 'Internal Chromium OS Build Team Options',
604 'Caution: these are for meant for the Chromium OS build team only')
605 group.add_option('--buildbot-log-version', default=False, action='store_true',
606 help='Log SDK version for buildbot consumption')
607
608 return (parser, commands)
609
610
611def main(argv):
612 conf = cros_build_lib.LoadKeyValueFile(
613 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
614 ignore_missing=True)
615 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
616 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
617 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700618 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800619
620 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500621 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800622
Brian Harring1790ac42012-09-23 08:53:33 -0700623 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700624 if host != 'x86_64':
625 parser.error(
626 "cros_sdk is currently only supported on x86_64; you're running"
627 " %s. Please find a x86_64 machine." % (host,))
628
Josh Triplett472a4182013-03-08 11:48:57 -0800629 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
630 if options.proxy_sim:
631 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800632
David James471532c2013-01-21 10:23:31 -0800633 _ReExecuteIfNeeded([sys.argv[0]] + argv)
Mike Frysinger80dfce92014-04-21 10:58:53 -0400634 if options.ns_pid:
635 first_pid = _CreatePidNamespace()
636 else:
637 first_pid = None
David James471532c2013-01-21 10:23:31 -0800638
Brian Harring218e13c2012-10-10 16:21:26 -0700639 # Expand out the aliases...
640 if options.replace:
641 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800642
Brian Harring218e13c2012-10-10 16:21:26 -0700643 if options.bootstrap:
644 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800645
Brian Harring218e13c2012-10-10 16:21:26 -0700646 # If a command is not given, default to enter.
647 options.enter |= not any(getattr(options, x.dest)
648 for x in commands.option_list)
649 options.enter |= bool(chroot_command)
650
651 if options.enter and options.delete and not options.create:
652 parser.error("Trying to enter the chroot when --delete "
653 "was specified makes no sense.")
654
655 # Finally, discern if we need to create the chroot.
656 chroot_exists = os.path.exists(options.chroot)
657 if options.create or options.enter:
658 # Only create if it's being wiped, or if it doesn't exist.
659 if not options.delete and chroot_exists:
660 options.create = False
661 else:
662 options.download = True
663
664 # Finally, flip create if necessary.
665 if options.enter:
666 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800667
Brian Harringb938c782012-02-29 15:14:38 -0800668 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700669 sdk_version = (bootstrap_latest_version if options.bootstrap
670 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800671 else:
672 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500673 if options.buildbot_log_version:
674 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800675
Brian Harring1790ac42012-09-23 08:53:33 -0700676 # Based on selections, fetch the tarball.
677 if options.sdk_url:
678 urls = [options.sdk_url]
679 elif options.bootstrap:
680 urls = GetStage3Urls(sdk_version)
681 else:
682 urls = GetArchStageTarballs(sdk_version)
683
Brian Harringb6cf9142012-09-01 20:43:17 -0700684 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800685 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700686 '.%s_lock' % os.path.basename(options.chroot))
Mike Frysinger80dfce92014-04-21 10:58:53 -0400687 with cgroups.SimpleContainChildren('cros_sdk', pid=first_pid):
David James56e6c2c2012-10-24 23:54:41 -0700688 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700689
Josh Triplett472a4182013-03-08 11:48:57 -0800690 if options.proxy_sim:
691 _ProxySimSetup(options)
692
David James56e6c2c2012-10-24 23:54:41 -0700693 if options.delete and os.path.exists(options.chroot):
694 lock.write_lock()
695 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800696
David James56e6c2c2012-10-24 23:54:41 -0700697 sdk_cache = os.path.join(options.cache_dir, 'sdks')
698 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700699 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700700
David James56e6c2c2012-10-24 23:54:41 -0700701 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500702 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700703 if not os.path.exists(src):
704 osutils.SafeMakedirs(target)
705 continue
706 lock.write_lock(
707 "Upgrade to %r needed but chroot is locked; please exit "
708 "all instances so this upgrade can finish." % src)
709 if not os.path.exists(src):
710 # Note that while waiting for the write lock, src may've vanished;
711 # it's a rare race during the upgrade process that's a byproduct
712 # of us avoiding taking a write lock to do the src check. If we
713 # took a write lock for that check, it would effectively limit
714 # all cros_sdk for a chroot to a single instance.
715 osutils.SafeMakedirs(target)
716 elif not os.path.exists(target):
717 # Upgrade occurred, but a reversion, or something whacky
718 # occurred writing to the old location. Wipe and continue.
719 os.rename(src, target)
720 else:
721 # Upgrade occurred once already, but either a reversion or
722 # some before/after separate cros_sdk usage is at play.
723 # Wipe and continue.
724 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700725
David James56e6c2c2012-10-24 23:54:41 -0700726 if options.download:
727 lock.write_lock()
728 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700729
David James56e6c2c2012-10-24 23:54:41 -0700730 if options.create:
731 lock.write_lock()
732 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
733 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700734
David James56e6c2c2012-10-24 23:54:41 -0700735 if options.enter:
736 lock.read_lock()
737 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
738 options.chrome_root_mount, chroot_command)