1#!/bin/bash -e 2 3# Copyright (c) 2012 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7# This script installs Debian-derived distributions in a chroot environment. 8# It can for example be used to have an accurate 32bit build and test 9# environment when otherwise working on a 64bit machine. 10# N. B. it is unlikely that this script will ever work on anything other than a 11# Debian-derived system. 12 13# Older Debian based systems had both "admin" and "adm" groups, with "admin" 14# apparently being used in more places. Newer distributions have standardized 15# on just the "adm" group. Check /etc/group for the prefered name of the 16# administrator group. 17admin=$(grep '^admin:' /etc/group >&/dev/null && echo admin || echo adm) 18 19usage() { 20 echo "usage: ${0##*/} [-m mirror] [-g group,...] [-s] [-c]" 21 echo "-b dir additional directories that should be bind mounted," 22 echo ' or "NONE".' 23 echo " Default: if local filesystems present, ask user for help" 24 echo "-g group,... groups that can use the chroot unauthenticated" 25 echo " Default: '${admin}' and current user's group ('$(id -gn)')" 26 echo "-l List all installed chroot environments" 27 echo "-m mirror an alternate repository mirror for package downloads" 28 echo "-s configure default deb-srcs" 29 echo "-c always copy 64bit helper binaries to 32bit chroot" 30 echo "-h this help message" 31} 32 33process_opts() { 34 local OPTNAME OPTIND OPTERR OPTARG 35 while getopts ":b:g:lm:sch" OPTNAME; do 36 case "$OPTNAME" in 37 b) 38 if [ "${OPTARG}" = "NONE" -a -z "${bind_mounts}" ]; then 39 bind_mounts="${OPTARG}" 40 else 41 if [ "${bind_mounts}" = "NONE" -o "${OPTARG}" = "${OPTARG#/}" -o \ 42 ! -d "${OPTARG}" ]; then 43 echo "Invalid -b option(s)" 44 usage 45 exit 1 46 fi 47 bind_mounts="${bind_mounts} 48${OPTARG} ${OPTARG} none rw,bind 0 0" 49 fi 50 ;; 51 g) 52 [ -n "${OPTARG}" ] && 53 chroot_groups="${chroot_groups}${chroot_groups:+,}${OPTARG}" 54 ;; 55 l) 56 list_all_chroots 57 exit 58 ;; 59 m) 60 if [ -n "${mirror}" ]; then 61 echo "You can only specify exactly one mirror location" 62 usage 63 exit 1 64 fi 65 mirror="$OPTARG" 66 ;; 67 s) 68 add_srcs="y" 69 ;; 70 c) 71 copy_64="y" 72 ;; 73 h) 74 usage 75 exit 0 76 ;; 77 \:) 78 echo "'-$OPTARG' needs an argument." 79 usage 80 exit 1 81 ;; 82 *) 83 echo "invalid command-line option: $OPTARG" 84 usage 85 exit 1 86 ;; 87 esac 88 done 89 90 if [ $# -ge ${OPTIND} ]; then 91 eval echo "Unexpected command line argument: \${${OPTIND}}" 92 usage 93 exit 1 94 fi 95} 96 97list_all_chroots() { 98 for i in /var/lib/chroot/*; do 99 i="${i##*/}" 100 [ "${i}" = "*" ] && continue 101 [ -x "/usr/local/bin/${i%bit}" ] || continue 102 grep -qs "^\[${i%bit}\]\$" /etc/schroot/schroot.conf || continue 103 [ -r "/etc/schroot/script-${i}" -a \ 104 -r "/etc/schroot/mount-${i}" ] || continue 105 echo "${i%bit}" 106 done 107} 108 109getkey() { 110 ( 111 trap 'stty echo -iuclc icanon 2>/dev/null' EXIT INT TERM QUIT HUP 112 stty -echo iuclc -icanon 2>/dev/null 113 dd count=1 bs=1 2>/dev/null 114 ) 115} 116 117chr() { 118 printf "\\$(printf '%03o' "$1")" 119} 120 121ord() { 122 printf '%d' $(printf '%c' "$1" | od -tu1 -An) 123} 124 125is_network_drive() { 126 stat -c %T -f "$1/" 2>/dev/null | 127 egrep -qs '^nfs|cifs|smbfs' 128} 129 130# Check that we are running as a regular user 131[ "$(id -nu)" = root ] && { 132 echo "Run this script as a regular user and provide your \"sudo\"" \ 133 "password if requested" >&2 134 exit 1 135} 136 137process_opts "$@" 138 139echo "This script will help you through the process of installing a" 140echo "Debian or Ubuntu distribution in a chroot environment. You will" 141echo "have to provide your \"sudo\" password when requested." 142echo 143 144# Error handler 145trap 'exit 1' INT TERM QUIT HUP 146trap 'sudo apt-get clean; tput bel; echo; echo Failed' EXIT 147 148# Install any missing applications that this script relies on. If these packages 149# are already installed, don't force another "apt-get install". That would 150# prevent them from being auto-removed, if they ever become eligible for that. 151# And as this script only needs the packages once, there is no good reason to 152# introduce a hard dependency on things such as dchroot and debootstrap. 153dep= 154for i in dchroot debootstrap libwww-perl; do 155 [ -d /usr/share/doc/"$i" ] || dep="$dep $i" 156done 157[ -n "$dep" ] && sudo apt-get -y install $dep 158sudo apt-get -y install schroot 159 160# Create directory for chroot 161sudo mkdir -p /var/lib/chroot 162 163# Find chroot environments that can be installed with debootstrap 164targets="$(cd /usr/share/debootstrap/scripts 165 ls | grep '^[a-z]*$')" 166 167# Ask user to pick one of the available targets 168echo "The following targets are available to be installed in a chroot:" 169j=1; for i in $targets; do 170 printf '%4d: %s\n' "$j" "$i" 171 j=$(($j+1)) 172done 173while :; do 174 printf "Which target would you like to install: " 175 read n 176 [ "$n" -gt 0 -a "$n" -lt "$j" ] >&/dev/null && break 177done 178j=1; for i in $targets; do 179 [ "$j" -eq "$n" ] && { distname="$i"; break; } 180 j=$(($j+1)) 181done 182echo 183 184# On x86-64, ask whether the user wants to install x86-32 or x86-64 185archflag= 186arch= 187if [ "$(uname -m)" = x86_64 ]; then 188 while :; do 189 echo "You are running a 64bit kernel. This allows you to install either a" 190 printf "32bit or a 64bit chroot environment. %s" \ 191 "Which one do you want (32, 64) " 192 read arch 193 [ "${arch}" == 32 -o "${arch}" == 64 ] && break 194 done 195 [ "${arch}" == 32 ] && archflag="--arch i386" || archflag="--arch amd64" 196 arch="${arch}bit" 197 echo 198fi 199target="${distname}${arch}" 200 201# Don't accidentally overwrite an existing installation 202[ -d /var/lib/chroot/"${target}" ] && { 203 while :; do 204 echo "This chroot already exists on your machine." 205 if schroot -l --all-sessions 2>&1 | 206 sed 's/^session://' | 207 grep -qs "^${target%bit}-"; then 208 echo "And it appears to be in active use. Terminate all programs that" 209 echo "are currently using the chroot environment and then re-run this" 210 echo "script." 211 echo "If you still get an error message, you might have stale mounts" 212 echo "that you forgot to delete. You can always clean up mounts by" 213 echo "executing \"${target%bit} -c\"." 214 exit 1 215 fi 216 echo "I can abort installation, I can overwrite the existing chroot," 217 echo "or I can delete the old one and then exit. What would you like to" 218 printf "do (a/o/d)? " 219 read choice 220 case "${choice}" in 221 a|A) exit 1;; 222 o|O) sudo rm -rf "/var/lib/chroot/${target}"; break;; 223 d|D) sudo rm -rf "/var/lib/chroot/${target}" \ 224 "/usr/local/bin/${target%bit}" \ 225 "/etc/schroot/mount-${target}" \ 226 "/etc/schroot/script-${target}" 227 sudo sed -ni '/^[[]'"${target%bit}"']$/,${ 228 :1;n;/^[[]/b2;b1;:2;p;n;b2};p' \ 229 "/etc/schroot/schroot.conf" 230 trap '' INT TERM QUIT HUP 231 trap '' EXIT 232 echo "Deleted!" 233 exit 0;; 234 esac 235 done 236 echo 237} 238sudo mkdir -p /var/lib/chroot/"${target}" 239 240# Offer to include additional standard repositories for Ubuntu-based chroots. 241alt_repos= 242grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && { 243 while :; do 244 echo "Would you like to add ${distname}-updates and ${distname}-security " 245 printf "to the chroot's sources.list (y/n)? " 246 read alt_repos 247 case "${alt_repos}" in 248 y|Y) 249 alt_repos="y" 250 break 251 ;; 252 n|N) 253 break 254 ;; 255 esac 256 done 257 echo 258} 259 260# Check for non-standard file system mount points and ask the user whether 261# they should be imported into the chroot environment 262# We limit to the first 26 mount points that much some basic heuristics, 263# because a) that allows us to enumerate choices with a single character, 264# and b) if we find more than 26 mount points, then these are probably 265# false-positives and something is very unusual about the system's 266# configuration. No need to spam the user with even more information that 267# is likely completely irrelevant. 268if [ -z "${bind_mounts}" ]; then 269 mounts="$(awk '$2 != "/" && $2 !~ "^/boot" && $2 !~ "^/home" && 270 $2 !~ "^/media" && $2 !~ "^/run" && 271 ($3 ~ "ext[2-4]" || $3 == "reiserfs" || $3 == "btrfs" || 272 $3 == "xfs" || $3 == "jfs" || $3 == "u?msdos" || 273 $3 == "v?fat" || $3 == "hfs" || $3 == "ntfs" || 274 $3 ~ "nfs[4-9]?" || $3 == "smbfs" || $3 == "cifs") { 275 print $2 276 }' /proc/mounts | 277 head -n26)" 278 if [ -n "${mounts}" ]; then 279 echo "You appear to have non-standard mount points that you" 280 echo "might want to import into the chroot environment:" 281 echo 282 sel= 283 while :; do 284 # Print a menu, listing all non-default mounts of local or network 285 # file systems. 286 j=1; for m in ${mounts}; do 287 c="$(printf $(printf '\\%03o' $((64+$j))))" 288 echo "$sel" | grep -qs $c && 289 state="mounted in chroot" || state="$(tput el)" 290 printf " $c) %-40s${state}\n" "$m" 291 j=$(($j+1)) 292 done 293 # Allow user to interactively (de-)select any of the entries 294 echo 295 printf "Select mount points that you want to be included or press %s" \ 296 "SPACE to continue" 297 c="$(getkey | tr a-z A-Z)" 298 [ "$c" == " " ] && { echo; echo; break; } 299 if [ -z "$c" ] || 300 [ "$c" '<' 'A' -o $(ord "$c") -gt $((64 + $(ord "$j"))) ]; then 301 # Invalid input, ring the console bell 302 tput bel 303 else 304 # Toggle the selection for the given entry 305 if echo "$sel" | grep -qs $c; then 306 sel="$(printf "$sel" | sed "s/$c//")" 307 else 308 sel="$sel$c" 309 fi 310 fi 311 # Reposition cursor to the top of the list of entries 312 tput cuu $(($j + 1)) 313 echo 314 done 315 fi 316 j=1; for m in ${mounts}; do 317 c="$(chr $(($j + 64)))" 318 if echo "$sel" | grep -qs $c; then 319 bind_mounts="${bind_mounts}$m $m none rw,bind 0 0 320" 321 fi 322 j=$(($j+1)) 323 done 324fi 325 326# Remove stale entry from /etc/schroot/schroot.conf. Entries start 327# with the target name in square brackets, followed by an arbitrary 328# number of lines. The entry stops when either the end of file has 329# been reached, or when the beginning of a new target is encountered. 330# This means, we cannot easily match for a range of lines in 331# "sed". Instead, we actually have to iterate over each line and check 332# whether it is the beginning of a new entry. 333sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p' \ 334 /etc/schroot/schroot.conf 335 336# Download base system. This takes some time 337if [ -z "${mirror}" ]; then 338 grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && 339 mirror="http://archive.ubuntu.com/ubuntu" || 340 mirror="http://ftp.us.debian.org/debian" 341fi 342 343sudo ${http_proxy:+http_proxy="${http_proxy}"} debootstrap ${archflag} \ 344 "${distname}" "/var/lib/chroot/${target}" "$mirror" 345 346# Add new entry to /etc/schroot/schroot.conf 347grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && 348 brand="Ubuntu" || brand="Debian" 349if [ -z "${chroot_groups}" ]; then 350 chroot_groups="${admin},$(id -gn)" 351fi 352# Older versions of schroot wanted a "priority=" line, whereas recent 353# versions deprecate "priority=" and warn if they see it. We don't have 354# a good feature test, but scanning for the string "priority=" in the 355# existing "schroot.conf" file is a good indication of what to do. 356priority=$(grep -qs 'priority=' /etc/schroot/schroot.conf && 357 echo 'priority=3' || :) 358sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF 359[${target%bit}] 360description=${brand} ${distname} ${arch} 361type=directory 362directory=/var/lib/chroot/${target} 363users=root 364groups=${chroot_groups} 365root-groups=${chroot_groups} 366personality=linux$([ "${arch}" != 64bit ] && echo 32) 367script-config=script-${target} 368${priority} 369 370EOF 371 372# Set up a list of mount points that is specific to this 373# chroot environment. 374sed '/^FSTAB=/s,"[^"]*","/etc/schroot/mount-'"${target}"'",' \ 375 /etc/schroot/script-defaults | 376 sudo sh -c 'cat >/etc/schroot/script-'"${target}" 377sed '\,^/home[/[:space:]],s/\([,[:space:]]\)bind[[:space:]]/\1rbind /' \ 378 /etc/schroot/mount-defaults | 379 sudo sh -c 'cat > /etc/schroot/mount-'"${target}" 380 381# Add the extra mount points that the user told us about 382[ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] && 383 printf "${bind_mounts}" | 384 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 385 386# If this system has a "/media" mountpoint, import it into the chroot 387# environment. Most modern distributions use this mount point to 388# automatically mount devices such as CDROMs, USB sticks, etc... 389if [ -d /media ] && 390 ! grep -qs '^/media' /etc/schroot/mount-"${target}"; then 391 echo '/media /media none rw,rbind 0 0' | 392 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 393fi 394 395# Share /dev/shm, /run and /run/shm. 396grep -qs '^/dev/shm' /etc/schroot/mount-"${target}" || 397 echo '/dev/shm /dev/shm none rw,bind 0 0' | 398 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 399if [ ! -d "/var/lib/chroot/${target}/run" ] && 400 ! grep -qs '^/run' /etc/schroot/mount-"${target}"; then 401 echo '/run /run none rw,bind 0 0' | 402 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 403fi 404if ! grep -qs '^/run/shm' /etc/schroot/mount-"${target}"; then 405 { [ -d /run ] && echo '/run/shm /run/shm none rw,bind 0 0' || 406 echo '/dev/shm /run/shm none rw,bind 0 0'; } | 407 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 408fi 409 410# Set up a special directory that changes contents depending on the target 411# that is executing. 412d="$(readlink -f "${HOME}/chroot" 2>/dev/null || echo "${HOME}/chroot")" 413s="${d}/.${target}" 414echo "${s} ${d} none rw,bind 0 0" | 415 sudo sh -c 'cat >>/etc/schroot/mount-'"${target}" 416mkdir -p "${s}" 417 418# Install a helper script to launch commands in the chroot 419sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<'EOF' 420#!/bin/bash 421 422chroot="${0##*/}" 423 424wrap() { 425 # Word-wrap the text passed-in on stdin. Optionally, on continuation lines 426 # insert the same number of spaces as the number of characters in the 427 # parameter(s) passed to this function. 428 # If the "fold" program cannot be found, or if the actual width of the 429 # terminal cannot be determined, this function doesn't attempt to do any 430 # wrapping. 431 local f="$(type -P fold)" 432 [ -z "${f}" ] && { cat; return; } 433 local c="$(stty -a </dev/tty 2>/dev/null | 434 sed 's/.*columns[[:space:]]*\([0-9]*\).*/\1/;t;d')" 435 [ -z "${c}" ] && { cat; return; } 436 local i="$(echo "$*"|sed 's/./ /g')" 437 local j="$(printf %s "${i}"|wc -c)" 438 if [ "${c}" -gt "${j}" ]; then 439 dd bs=1 count="${j}" 2>/dev/null 440 "${f}" -sw "$((${c}-${j}))" | sed '2,$s/^/'"${i}"'/' 441 else 442 "${f}" -sw "${c}" 443 fi 444} 445 446help() { 447 echo "Usage ${0##*/} [-h|--help] [-c|--clean] [-C|--clean-all] [-l|--list] [--] args" | wrap "Usage ${0##*/} " 448 echo " help: print this message" | wrap " " 449 echo " list: list all known chroot environments" | wrap " " 450 echo " clean: remove all old chroot sessions for \"${chroot}\"" | wrap " " 451 echo " clean-all: remove all old chroot sessions for all environments" | wrap " " 452 exit 0 453} 454 455clean() { 456 local s t rc 457 rc=0 458 for s in $(schroot -l --all-sessions); do 459 if [ -n "$1" ]; then 460 t="${s#session:}" 461 [ "${t#${chroot}-}" == "${t}" ] && continue 462 fi 463 if ls -l /proc/*/{cwd,fd} 2>/dev/null | 464 fgrep -qs "/var/lib/schroot/mount/${t}"; then 465 echo "Session \"${t}\" still has active users, not cleaning up" | wrap 466 rc=1 467 continue 468 fi 469 sudo schroot -c "${s}" -e || rc=1 470 done 471 exit ${rc} 472} 473 474list() { 475 for e in $(schroot -l); do 476 e="${e#chroot:}" 477 [ -x "/usr/local/bin/${e}" ] || continue 478 if schroot -l --all-sessions 2>/dev/null | 479 sed 's/^session://' | 480 grep -qs "^${e}-"; then 481 echo "${e} is currently active" 482 else 483 echo "${e}" 484 fi 485 done 486 exit 0 487} 488 489while [ "$#" -ne 0 ]; do 490 case "$1" in 491 --) shift; break;; 492 -h|--help) shift; help;; 493 -l|--list) shift; list;; 494 -c|--clean) shift; clean "${chroot}";; 495 -C|--clean-all) shift; clean;; 496 *) break;; 497 esac 498done 499 500# Start a new chroot session and keep track of the session id. We inject this 501# id into all processes that run inside the chroot. Unless they go out of their 502# way to clear their environment, we can then later identify our child and 503# grand-child processes by scanning their environment. 504session="$(schroot -c "${chroot}" -b)" 505export CHROOT_SESSION_ID="${session}" 506 507if [ $# -eq 0 ]; then 508 # Run an interactive shell session 509 schroot -c "${session}" -r -p 510else 511 # Run a command inside of the chroot environment 512 p="$1"; shift 513 schroot -c "${session}" -r -p "$p" -- "$@" 514fi 515rc=$? 516 517# Compute the inode of the root directory inside of the chroot environment. 518i=$(schroot -c "${session}" -r -p ls -- -id /proc/self/root/. | 519 awk '{ print $1 }') 2>/dev/null 520other_pids= 521while [ -n "$i" ]; do 522 # Identify processes by the inode number of their root directory. Then 523 # remove all processes that we know belong to other sessions. We use 524 # "sort | uniq -u" to do what amounts to a "set substraction operation". 525 pids=$({ ls -id1 /proc/*/root/. 2>/dev/null | 526 sed -e 's,^[^0-9]*'$i'.*/\([1-9][0-9]*\)/.*$,\1, 527 t 528 d'; 529 echo "${other_pids}"; 530 echo "${other_pids}"; } | sort | uniq -u) >/dev/null 2>&1 531 # Kill all processes that are still left running in the session. This is 532 # typically an assortment of daemon processes that were started 533 # automatically. They result in us being unable to tear down the session 534 # cleanly. 535 [ -z "${pids}" ] && break 536 for j in $pids; do 537 # Unfortunately, the way that schroot sets up sessions has the 538 # side-effect of being unable to tell one session apart from another. 539 # This can result in us attempting to kill processes in other sessions. 540 # We make a best-effort to avoid doing so. 541 k="$( ( xargs -0 -n1 </proc/$j/environ ) 2>/dev/null | 542 sed 's/^CHROOT_SESSION_ID=/x/;t1;d;:1;q')" 543 if [ -n "${k}" -a "${k#x}" != "${session}" ]; then 544 other_pids="${other_pids} 545${j}" 546 continue 547 fi 548 kill -9 $pids 549 done 550done 551# End the chroot session. This should clean up all temporary files. But if we 552# earlier failed to terminate all (daemon) processes inside of the session, 553# deleting the session could fail. When that happens, the user has to manually 554# clean up the stale files by invoking us with "--clean" after having killed 555# all running processes. 556schroot -c "${session}" -e 557exit $rc 558EOF 559sudo chown root:root /usr/local/bin/"${target%bit}" 560sudo chmod 755 /usr/local/bin/"${target%bit}" 561 562# Add the standard Ubuntu update repositories if requested. 563[ "${alt_repos}" = "y" -a \ 564 -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && 565sudo sed -i '/^deb .* [^ -]\+ main$/p 566 s/^\(deb .* [^ -]\+\) main/\1-security main/ 567 p 568 t1 569 d 570 :1;s/-security main/-updates main/ 571 t 572 d' "/var/lib/chroot/${target}/etc/apt/sources.list" 573 574# Add a few more repositories to the chroot 575[ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && 576sudo sed -i 's/ main$/ main restricted universe multiverse/' \ 577 "/var/lib/chroot/${target}/etc/apt/sources.list" 578 579# Add the Ubuntu "partner" repository, if available 580if [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && 581 HEAD "http://archive.canonical.com/ubuntu/dists/${distname}/partner" \ 582 >&/dev/null; then 583 sudo sh -c ' 584 echo "deb http://archive.canonical.com/ubuntu" \ 585 "'"${distname}"' partner" \ 586 >>"/var/lib/chroot/'"${target}"'/etc/apt/sources.list"' 587fi 588 589# Add source repositories, if the user requested we do so 590[ "${add_srcs}" = "y" -a \ 591 -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && 592sudo sed -i '/^deb[^-]/p 593 s/^deb\([^-]\)/deb-src\1/' \ 594 "/var/lib/chroot/${target}/etc/apt/sources.list" 595 596# Set apt proxy if host has set http_proxy 597if [ -n "${http_proxy}" ]; then 598 sudo sh -c ' 599 echo "Acquire::http::proxy \"'"${http_proxy}"'\";" \ 600 >>"/var/lib/chroot/'"${target}"'/etc/apt/apt.conf"' 601fi 602 603# Update packages 604sudo "/usr/local/bin/${target%bit}" /bin/sh -c ' 605 apt-get update; apt-get -y dist-upgrade' || : 606 607# Install a couple of missing packages 608for i in debian-keyring ubuntu-keyring locales sudo; do 609 [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] || 610 sudo "/usr/local/bin/${target%bit}" apt-get -y install "$i" || : 611done 612 613# Configure locales 614sudo "/usr/local/bin/${target%bit}" /bin/sh -c ' 615 l='"${LANG:-en_US}"'; l="${l%%.*}" 616 [ -r /etc/locale.gen ] && 617 sed -i "s/^# \($l\)/\1/" /etc/locale.gen 618 locale-gen $LANG en_US en_US.UTF-8' || : 619 620# Enable multi-arch support, if available 621sudo "/usr/local/bin/${target%bit}" dpkg --assert-multi-arch >&/dev/null && 622 [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && { 623 sudo sed -i 's/ / [arch=amd64,i386] /' \ 624 "/var/lib/chroot/${target}/etc/apt/sources.list" 625 [ -d /var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/ ] && 626 sudo "/usr/local/bin/${target%bit}" dpkg --add-architecture \ 627 $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) >&/dev/null || 628 echo foreign-architecture \ 629 $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) | 630 sudo sh -c \ 631 "cat >'/var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/multiarch'" 632} 633 634# Configure "sudo" package 635sudo "/usr/local/bin/${target%bit}" /bin/sh -c ' 636 egrep -qs '"'^$(id -nu) '"' /etc/sudoers || 637 echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers' 638 639# Install a few more commonly used packages 640sudo "/usr/local/bin/${target%bit}" apt-get -y install \ 641 autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool \ 642 strace 643 644# If running a 32bit environment on a 64bit machine, install a few binaries 645# as 64bit. This is only done automatically if the chroot distro is the same as 646# the host, otherwise there might be incompatibilities in build settings or 647# runtime dependencies. The user can force it with the '-c' flag. 648host_distro=$(grep -s DISTRIB_CODENAME /etc/lsb-release | \ 649 cut -d "=" -f 2) 650if [ "${copy_64}" = "y" -o \ 651 "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \ 652 file /bin/bash 2>/dev/null | grep -q x86-64; then 653 readlinepkg=$(sudo "/usr/local/bin/${target%bit}" sh -c \ 654 'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1') 655 sudo "/usr/local/bin/${target%bit}" apt-get -y install \ 656 lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1 657 dep= 658 for i in binutils gdb; do 659 [ -d /usr/share/doc/"$i" ] || dep="$dep $i" 660 done 661 [ -n "$dep" ] && sudo apt-get -y install $dep 662 sudo mkdir -p "/var/lib/chroot/${target}/usr/local/lib/amd64" 663 for i in libbfd libpython; do 664 lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } | 665 grep -s "$i" | awk '{ print $3 }')" 666 if [ -n "$lib" -a -r "$lib" ]; then 667 sudo cp "$lib" "/var/lib/chroot/${target}/usr/local/lib/amd64" 668 fi 669 done 670 for lib in libssl libcrypt; do 671 for path in /usr/lib /usr/lib/x86_64-linux-gnu; do 672 sudo cp $path/$lib* \ 673 "/var/lib/chroot/${target}/usr/local/lib/amd64/" >&/dev/null || : 674 done 675 done 676 for i in gdb ld; do 677 sudo cp /usr/bin/$i "/var/lib/chroot/${target}/usr/local/lib/amd64/" 678 sudo sh -c "cat >'/var/lib/chroot/${target}/usr/local/bin/$i'" <<EOF 679#!/bin/sh 680exec /lib64/ld-linux-x86-64.so.2 --library-path /usr/local/lib/amd64 \ 681 /usr/local/lib/amd64/$i "\$@" 682EOF 683 sudo chmod 755 "/var/lib/chroot/${target}/usr/local/bin/$i" 684 done 685fi 686 687 688# If the install-build-deps.sh script can be found, offer to run it now 689script="$(dirname $(readlink -f "$0"))/install-build-deps.sh" 690if [ -x "${script}" ]; then 691 while :; do 692 echo 693 echo "If you plan on building Chrome inside of the new chroot environment," 694 echo "you now have to install the build dependencies. Do you want me to" 695 printf "start the script that does this for you (y/n)? " 696 read install_deps 697 case "${install_deps}" in 698 y|Y) 699 echo 700 # We prefer running the script in-place, but this might not be 701 # possible, if it lives on a network filesystem that denies 702 # access to root. 703 tmp_script= 704 if ! sudo /usr/local/bin/"${target%bit}" \ 705 sh -c "[ -x '${script}' ]" >&/dev/null; then 706 tmp_script="/tmp/${script##*/}" 707 cp "${script}" "${tmp_script}" 708 fi 709 # Some distributions automatically start an instance of the system- 710 # wide dbus daemon, cron daemon or of the logging daemon, when 711 # installing the Chrome build depencies. This prevents the chroot 712 # session from being closed. So, we always try to shut down any running 713 # instance of dbus and rsyslog. 714 sudo /usr/local/bin/"${target%bit}" sh -c "${script} --no-lib32; 715 rc=$?; 716 /etc/init.d/cron stop >/dev/null 2>&1 || :; 717 /etc/init.d/rsyslog stop >/dev/null 2>&1 || :; 718 /etc/init.d/dbus stop >/dev/null 2>&1 || :; 719 exit $rc" 720 rc=$? 721 [ -n "${tmp_script}" ] && rm -f "${tmp_script}" 722 [ $rc -ne 0 ] && exit $rc 723 break 724 ;; 725 n|N) 726 break 727 ;; 728 esac 729 done 730 echo 731fi 732 733# Check whether ~/chroot is on a (slow) network file system and offer to 734# relocate it. Also offer relocation, if the user appears to have multiple 735# spindles (as indicated by "${bind_mount}" being non-empty). 736# We only offer this option, if it doesn't look as if a chroot environment 737# is currently active. Otherwise, relocation is unlikely to work and it 738# can be difficult for the user to recover from the failed attempt to relocate 739# the ~/chroot directory. 740# We don't aim to solve this problem for every configuration, 741# but try to help with the common cases. For more advanced configuration 742# options, the user can always manually adjust things. 743mkdir -p "${HOME}/chroot/" 744if [ ! -h "${HOME}/chroot" ] && 745 ! egrep -qs '^[^[:space:]]*/chroot' /etc/fstab && 746 { [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] || 747 is_network_drive "${HOME}/chroot"; } && 748 ! egrep -qs '/var/lib/[^/]*chroot/.*/chroot' /proc/mounts; then 749 echo "${HOME}/chroot is currently located on the same device as your" 750 echo "home directory." 751 echo "This might not be what you want. Do you want me to move it somewhere" 752 echo "else?" 753 # If the computer has multiple spindles, many users configure all or part of 754 # the secondary hard disk to be writable by the primary user of this machine. 755 # Make some reasonable effort to detect this type of configuration and 756 # then offer a good location for where to put the ~/chroot directory. 757 suggest= 758 for i in $(echo "${bind_mounts}"|cut -d ' ' -f 1); do 759 if [ -d "$i" -a -w "$i" -a \( ! -a "$i/chroot" -o -w "$i/chroot/." \) ] && 760 ! is_network_drive "$i"; then 761 suggest="$i" 762 else 763 for j in "$i/"*; do 764 if [ -d "$j" -a -w "$j" -a \ 765 \( ! -a "$j/chroot" -o -w "$j/chroot/." \) ] && 766 ! is_network_drive "$j"; then 767 suggest="$j" 768 else 769 for k in "$j/"*; do 770 if [ -d "$k" -a -w "$k" -a \ 771 \( ! -a "$k/chroot" -o -w "$k/chroot/." \) ] && 772 ! is_network_drive "$k"; then 773 suggest="$k" 774 break 775 fi 776 done 777 fi 778 [ -n "${suggest}" ] && break 779 done 780 fi 781 [ -n "${suggest}" ] && break 782 done 783 def_suggest="${HOME}" 784 if [ -n "${suggest}" ]; then 785 # For home directories that reside on network drives, make our suggestion 786 # the default option. For home directories that reside on a local drive, 787 # require that the user manually enters the new location. 788 if is_network_drive "${HOME}"; then 789 def_suggest="${suggest}" 790 else 791 echo "A good location would probably be in \"${suggest}\"" 792 fi 793 fi 794 while :; do 795 printf "Physical location [${def_suggest}]: " 796 read dir 797 [ -z "${dir}" ] && dir="${def_suggest}" 798 [ "${dir%%/}" == "${HOME%%/}" ] && break 799 if ! [ -d "${dir}" -a -w "${dir}" ] || 800 [ -a "${dir}/chroot" -a ! -w "${dir}/chroot/." ]; then 801 echo "Cannot write to ${dir}/chroot. Please try again" 802 else 803 mv "${HOME}/chroot" "${dir}/chroot" 804 ln -s "${dir}/chroot" "${HOME}/chroot" 805 for i in $(list_all_chroots); do 806 sudo "$i" mkdir -p "${dir}/chroot" 807 done 808 sudo sed -i "s,${HOME}/chroot,${dir}/chroot,g" /etc/schroot/mount-* 809 break 810 fi 811 done 812fi 813 814# Clean up package files 815sudo schroot -c "${target%bit}" -p -- apt-get clean 816sudo apt-get clean 817 818trap '' INT TERM QUIT HUP 819trap '' EXIT 820 821# Let the user know what we did 822cat <<EOF 823 824 825Successfully installed ${distname} ${arch} 826 827You can run programs inside of the chroot by invoking the 828"/usr/local/bin/${target%bit}" command. 829 830This command can be used with arguments, in order to just run a single 831program inside of the chroot environment (e.g. "${target%bit} make chrome") 832or without arguments, in order to run an interactive shell session inside 833of the chroot environment. 834 835If you need to run things as "root", you can use "sudo" (e.g. try 836"sudo ${target%bit} apt-get update"). 837 838Your home directory is shared between the host and the chroot. But I 839configured "${HOME}/chroot" to be private to the chroot environment. 840You can use it for files that need to differ between environments. This 841would be a good place to store binaries that you have built from your 842source files. 843 844For Chrome, this probably means you want to make your "out" directory a 845symbolic link that points somewhere inside of "${HOME}/chroot". 846 847You still need to run "gclient runhooks" whenever you switch from building 848outside of the chroot to inside of the chroot. But you will find that you 849don't have to repeatedly erase and then completely rebuild all your object 850and binary files. 851 852EOF 853