1# Copyright 2020 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Runs a command inside an NsJail sandbox for building Android. 16 17NsJail creates a user namespace sandbox where 18Android can be built in an isolated process. 19If no command is provided then it will open 20an interactive bash shell. 21""" 22 23import argparse 24import collections 25import os 26import re 27import subprocess 28from . import config 29from .overlay import BindMount 30from .overlay import BindOverlay 31 32_DEFAULT_META_ANDROID_DIR = 'LINUX/android' 33_DEFAULT_COMMAND = '/bin/bash' 34 35_SOURCE_MOUNT_POINT = '/src' 36_OUT_MOUNT_POINT = '/src/out' 37_DIST_MOUNT_POINT = '/dist' 38_META_MOUNT_POINT = '/meta' 39 40_CHROOT_MOUNT_POINTS = [ 41 'bin', 'sbin', 42 'etc/alternatives', 'etc/default', 'etc/perl', 43 'etc/ssl', 'etc/xml', 44 'lib', 'lib32', 'lib64', 'libx32', 45 'usr', 46] 47 48 49def run(command, 50 build_target, 51 nsjail_bin, 52 chroot, 53 overlay_config=None, 54 source_dir=os.getcwd(), 55 dist_dir=None, 56 build_id=None, 57 out_dir = None, 58 meta_root_dir = None, 59 meta_android_dir = _DEFAULT_META_ANDROID_DIR, 60 mount_local_device = False, 61 max_cpus=None, 62 extra_bind_mounts=[], 63 readonly_bind_mounts=[], 64 extra_nsjail_args=[], 65 dry_run=False, 66 quiet=False, 67 env=[], 68 nsjail_wrapper=[], 69 stdout=None, 70 stderr=None): 71 """Run inside an NsJail sandbox. 72 73 Args: 74 command: A list of strings with the command to run. 75 build_target: A string with the name of the build target to be prepared 76 inside the container. 77 nsjail_bin: A string with the path to the nsjail binary. 78 chroot: A string with the path to the chroot. 79 overlay_config: A string path to an overlay configuration file. 80 source_dir: A string with the path to the Android platform source. 81 dist_dir: A string with the path to the dist directory. 82 build_id: A string with the build identifier. 83 out_dir: An optional path to the Android build out folder. 84 meta_root_dir: An optional path to a folder containing the META build. 85 meta_android_dir: An optional path to the location where the META build expects 86 the Android build. This path must be relative to meta_root_dir. 87 mount_local_device: Whether to mount /dev/usb (and related) trees enabling 88 adb to run inside the jail 89 max_cpus: An integer with maximum number of CPUs. 90 extra_bind_mounts: An array of extra mounts in the 'source' or 'source:dest' syntax. 91 readonly_bind_mounts: An array of read only mounts in the 'source' or 'source:dest' syntax. 92 extra_nsjail_args: A list of strings that contain extra arguments to nsjail. 93 dry_run: If true, the command will be returned but not executed 94 quiet: If true, the function will not display the command and 95 will pass -quiet argument to nsjail 96 env: An array of environment variables to define in the jail in the `var=val` syntax. 97 nsjail_wrapper: A list of strings used to wrap the nsjail command. 98 stdout: the standard output for all printed messages. Valid values are None, a file 99 descriptor or file object. A None value means sys.stdout is used. 100 stderr: the standard error for all printed messages. Valid values are None, a file 101 descriptor or file object, and subprocess.STDOUT (which indicates that all stderr 102 should be redirected to stdout). A None value means sys.stderr is used. 103 104 Returns: 105 A list of strings with the command executed. 106 """ 107 108 109 nsjail_command = get_command( 110 command=command, 111 build_target=build_target, 112 nsjail_bin=nsjail_bin, 113 chroot=chroot, 114 cfg=config.factory(overlay_config), 115 source_dir=source_dir, 116 dist_dir=dist_dir, 117 build_id=build_id, 118 out_dir=out_dir, 119 meta_root_dir=meta_root_dir, 120 meta_android_dir=meta_android_dir, 121 mount_local_device=mount_local_device, 122 max_cpus=max_cpus, 123 extra_bind_mounts=extra_bind_mounts, 124 readonly_bind_mounts=readonly_bind_mounts, 125 extra_nsjail_args=extra_nsjail_args, 126 quiet=quiet, 127 env=env, 128 nsjail_wrapper=nsjail_wrapper) 129 130 run_command( 131 nsjail_command=nsjail_command, 132 mount_local_device=mount_local_device, 133 dry_run=dry_run, 134 quiet=quiet, 135 stdout=stdout, 136 stderr=stderr) 137 138 return nsjail_command 139 140def get_command(command, 141 build_target, 142 nsjail_bin, 143 chroot, 144 cfg=None, 145 source_dir=os.getcwd(), 146 dist_dir=None, 147 build_id=None, 148 out_dir = None, 149 meta_root_dir = None, 150 meta_android_dir = _DEFAULT_META_ANDROID_DIR, 151 mount_local_device = False, 152 max_cpus=None, 153 extra_bind_mounts=[], 154 readonly_bind_mounts=[], 155 extra_nsjail_args=[], 156 quiet=False, 157 env=[], 158 nsjail_wrapper=[]): 159 """Get command to run nsjail sandbox. 160 161 Args: 162 command: A list of strings with the command to run. 163 build_target: A string with the name of the build target to be prepared 164 inside the container. 165 nsjail_bin: A string with the path to the nsjail binary. 166 chroot: A string with the path to the chroot. 167 cfg: A config.Config instance or None. 168 source_dir: A string with the path to the Android platform source. 169 dist_dir: A string with the path to the dist directory. 170 build_id: A string with the build identifier. 171 out_dir: An optional path to the Android build out folder. 172 meta_root_dir: An optional path to a folder containing the META build. 173 meta_android_dir: An optional path to the location where the META build expects 174 the Android build. This path must be relative to meta_root_dir. 175 max_cpus: An integer with maximum number of CPUs. 176 extra_bind_mounts: An array of extra mounts in the 'source' or 'source:dest' syntax. 177 readonly_bind_mounts: An array of read only mounts in the 'source' or 'source:dest' syntax. 178 extra_nsjail_args: A list of strings that contain extra arguments to nsjail. 179 quiet: If true, the function will not display the command and 180 will pass -quiet argument to nsjail 181 env: An array of environment variables to define in the jail in the `var=val` syntax. 182 183 Returns: 184 A list of strings with the command to execute. 185 """ 186 script_dir = os.path.dirname(os.path.abspath(__file__)) 187 config_file = os.path.join(script_dir, 'nsjail.cfg') 188 189 # Run expects absolute paths 190 if out_dir: 191 out_dir = os.path.abspath(out_dir) 192 if dist_dir: 193 dist_dir = os.path.abspath(dist_dir) 194 if meta_root_dir: 195 meta_root_dir = os.path.abspath(meta_root_dir) 196 if source_dir: 197 source_dir = os.path.abspath(source_dir) 198 199 if nsjail_bin: 200 nsjail_bin = os.path.join(source_dir, nsjail_bin) 201 202 if chroot: 203 chroot = os.path.join(source_dir, chroot) 204 205 if meta_root_dir: 206 if not meta_android_dir or os.path.isabs(meta_android_dir): 207 raise ValueError('error: the provided meta_android_dir is not a path' 208 'relative to meta_root_dir.') 209 210 nsjail_command = nsjail_wrapper + [nsjail_bin, 211 '--env', 'USER=nobody', 212 '--config', config_file] 213 214 # By mounting the points individually that we need we reduce exposure and 215 # keep the chroot clean from artifacts 216 if chroot: 217 for mpoints in _CHROOT_MOUNT_POINTS: 218 source = os.path.join(chroot, mpoints) 219 dest = os.path.join('/', mpoints) 220 if os.path.exists(source): 221 nsjail_command.extend([ 222 '--bindmount_ro', '%s:%s' % (source, dest) 223 ]) 224 225 if build_id: 226 nsjail_command.extend(['--env', 'BUILD_NUMBER=%s' % build_id]) 227 if max_cpus: 228 nsjail_command.append('--max_cpus=%i' % max_cpus) 229 if quiet: 230 nsjail_command.append('--quiet') 231 232 whiteout_list = set() 233 if out_dir and ( 234 os.path.dirname(out_dir) == source_dir) and ( 235 os.path.basename(out_dir) != 'out'): 236 whiteout_list.add(os.path.abspath(out_dir)) 237 if not os.path.exists(out_dir): 238 os.makedirs(out_dir) 239 240 # Apply the overlay for the selected Android target to the source directory 241 # from the supplied config.Config instance (which may be None). 242 if cfg is not None: 243 overlay = BindOverlay(build_target, 244 source_dir, 245 cfg, 246 whiteout_list, 247 _SOURCE_MOUNT_POINT, 248 quiet=quiet) 249 bind_mounts = overlay.GetBindMounts() 250 else: 251 bind_mounts = collections.OrderedDict() 252 bind_mounts[_SOURCE_MOUNT_POINT] = BindMount(source_dir, False) 253 254 if out_dir: 255 bind_mounts[_OUT_MOUNT_POINT] = BindMount(out_dir, False) 256 257 if dist_dir: 258 bind_mounts[_DIST_MOUNT_POINT] = BindMount(dist_dir, False) 259 nsjail_command.extend([ 260 '--env', 'DIST_DIR=%s'%_DIST_MOUNT_POINT 261 ]) 262 263 if meta_root_dir: 264 bind_mounts[_META_MOUNT_POINT] = BindMount(meta_root_dir, False) 265 bind_mounts[os.path.join(_META_MOUNT_POINT, meta_android_dir)] = BindMount(source_dir, False) 266 if out_dir: 267 bind_mounts[os.path.join(_META_MOUNT_POINT, meta_android_dir, 'out')] = BindMount(out_dir, False) 268 269 for bind_destination, bind_mount in bind_mounts.items(): 270 if bind_mount.readonly: 271 nsjail_command.extend([ 272 '--bindmount_ro', bind_mount.source_dir + ':' + bind_destination 273 ]) 274 else: 275 nsjail_command.extend([ 276 '--bindmount', bind_mount.source_dir + ':' + bind_destination 277 ]) 278 279 if mount_local_device: 280 # Mount /dev/bus/usb and several /sys/... paths, which adb will examine 281 # while attempting to find the attached android device. These paths expose 282 # a lot of host operating system device space, so it's recommended to use 283 # the mount_local_device option only when you need to use adb (e.g., for 284 # atest or some other purpose). 285 nsjail_command.extend(['--bindmount', '/dev/bus/usb']) 286 nsjail_command.extend(['--bindmount', '/sys/bus/usb/devices']) 287 nsjail_command.extend(['--bindmount', '/sys/dev']) 288 nsjail_command.extend(['--bindmount', '/sys/devices']) 289 290 for mount in extra_bind_mounts: 291 nsjail_command.extend(['--bindmount', mount]) 292 for mount in readonly_bind_mounts: 293 nsjail_command.extend(['--bindmount_ro', mount]) 294 295 for var in env: 296 nsjail_command.extend(['--env', var]) 297 298 nsjail_command.extend(extra_nsjail_args) 299 300 nsjail_command.append('--') 301 nsjail_command.extend(command) 302 303 return nsjail_command 304 305def run_command(nsjail_command, 306 mount_local_device=False, 307 dry_run=False, 308 quiet=False, 309 stdout=None, 310 stderr=None): 311 """Run the provided nsjail command. 312 313 Args: 314 nsjail_command: A list of strings with the command to run. 315 mount_local_device: Whether to mount /dev/usb (and related) trees enabling 316 adb to run inside the jail 317 dry_run: If true, the command will be returned but not executed 318 quiet: If true, the function will not display the command and 319 will pass -quiet argument to nsjail 320 stdout: the standard output for all printed messages. Valid values are None, a file 321 descriptor or file object. A None value means sys.stdout is used. 322 stderr: the standard error for all printed messages. Valid values are None, a file 323 descriptor or file object, and subprocess.STDOUT (which indicates that all stderr 324 should be redirected to stdout). A None value means sys.stderr is used. 325 """ 326 327 if mount_local_device: 328 # A device can only communicate with one adb server at a time, so the adb server is 329 # killed on the host machine. 330 for line in subprocess.check_output(['ps','-eo','cmd']).decode().split('\n'): 331 if re.match(r'adb.*fork-server.*', line): 332 print('An adb server is running on your host machine. This server must be ' 333 'killed to use the --mount_local_device flag.') 334 print('Continue? [y/N]: ', end='') 335 if input().lower() != 'y': 336 exit() 337 subprocess.check_call(['adb', 'kill-server']) 338 339 if not quiet: 340 print('NsJail command:', file=stdout) 341 print(' '.join(nsjail_command), file=stdout) 342 343 if not dry_run: 344 subprocess.check_call(nsjail_command, stdout=stdout, stderr=stderr) 345 346def parse_args(): 347 """Parse command line arguments. 348 349 Returns: 350 An argparse.Namespace object. 351 """ 352 353 # Use the top level module docstring for the help description 354 parser = argparse.ArgumentParser( 355 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 356 parser.add_argument( 357 '--nsjail_bin', 358 required=True, 359 help='Path to NsJail binary.') 360 parser.add_argument( 361 '--chroot', 362 help='Path to the chroot to be used for building the Android' 363 'platform. This will be mounted as the root filesystem in the' 364 'NsJail sandbox.') 365 parser.add_argument( 366 '--overlay_config', 367 help='Path to the overlay configuration file.') 368 parser.add_argument( 369 '--source_dir', 370 default=os.getcwd(), 371 help='Path to Android platform source to be mounted as /src.') 372 parser.add_argument( 373 '--out_dir', 374 help='Full path to the Android build out folder. If not provided, uses ' 375 'the standard \'out\' folder in the current path.') 376 parser.add_argument( 377 '--meta_root_dir', 378 default='', 379 help='Full path to META folder. Default to \'\'') 380 parser.add_argument( 381 '--meta_android_dir', 382 default=_DEFAULT_META_ANDROID_DIR, 383 help='Relative path to the location where the META build expects ' 384 'the Android build. This path must be relative to meta_root_dir. ' 385 'Defaults to \'%s\'' % _DEFAULT_META_ANDROID_DIR) 386 parser.add_argument( 387 '--command', 388 default=_DEFAULT_COMMAND, 389 help='Command to run after entering the NsJail.' 390 'If not set then an interactive Bash shell will be launched') 391 parser.add_argument( 392 '--build_target', 393 required=True, 394 help='Android target selected for building') 395 parser.add_argument( 396 '--dist_dir', 397 help='Path to the Android dist directory. This is where' 398 'Android platform release artifacts will be written.' 399 'If unset then the Android platform default will be used.') 400 parser.add_argument( 401 '--build_id', 402 help='Build identifier what will label the Android platform' 403 'release artifacts.') 404 parser.add_argument( 405 '--max_cpus', 406 type=int, 407 help='Limit of concurrent CPU cores that the NsJail sandbox' 408 'can use. Defaults to unlimited.') 409 parser.add_argument( 410 '--bindmount', 411 type=str, 412 default=[], 413 action='append', 414 help='List of mountpoints to be mounted. Can be specified multiple times. ' 415 'Syntax: \'source\' or \'source:dest\'') 416 parser.add_argument( 417 '--bindmount_ro', 418 type=str, 419 default=[], 420 action='append', 421 help='List of mountpoints to be mounted read-only. Can be specified multiple times. ' 422 'Syntax: \'source\' or \'source:dest\'') 423 parser.add_argument( 424 '--dry_run', 425 action='store_true', 426 help='Prints the command without executing') 427 parser.add_argument( 428 '--quiet', '-q', 429 action='store_true', 430 help='Suppress debugging output') 431 parser.add_argument( 432 '--mount_local_device', 433 action='store_true', 434 help='If provided, mount locally connected Android USB devices inside ' 435 'the container. WARNING: Using this flag will cause the adb server to be ' 436 'killed on the host machine. WARNING: Using this flag exposes parts of ' 437 'the host /sys/... file system. Use only when you need adb.') 438 parser.add_argument( 439 '--env', '-e', 440 type=str, 441 default=[], 442 action='append', 443 help='Specify an environment variable to the NSJail sandbox. Can be specified ' 444 'muliple times. Syntax: var_name=value') 445 return parser.parse_args() 446 447def run_with_args(args): 448 """Run inside an NsJail sandbox. 449 450 Use the arguments from an argspace namespace. 451 452 Args: 453 An argparse.Namespace object. 454 455 Returns: 456 A list of strings with the commands executed. 457 """ 458 run(chroot=args.chroot, 459 nsjail_bin=args.nsjail_bin, 460 overlay_config=args.overlay_config, 461 source_dir=args.source_dir, 462 command=args.command.split(), 463 build_target=args.build_target, 464 dist_dir=args.dist_dir, 465 build_id=args.build_id, 466 out_dir=args.out_dir, 467 meta_root_dir=args.meta_root_dir, 468 meta_android_dir=args.meta_android_dir, 469 mount_local_device=args.mount_local_device, 470 max_cpus=args.max_cpus, 471 extra_bind_mounts=args.bindmount, 472 readonly_bind_mounts=args.bindmount_ro, 473 dry_run=args.dry_run, 474 quiet=args.quiet, 475 env=args.env) 476 477def main(): 478 run_with_args(parse_args()) 479 480if __name__ == '__main__': 481 main() 482