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 preferred 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                       "/etc/schroot/${target}"
228           sudo sed -ni '/^[[]'"${target%bit}"']$/,${
229                         :1;n;/^[[]/b2;b1;:2;p;n;b2};p' \
230                       "/etc/schroot/schroot.conf"
231           trap '' INT TERM QUIT HUP
232           trap '' EXIT
233           echo "Deleted!"
234           exit 0;;
235    esac
236  done
237  echo
238}
239sudo mkdir -p /var/lib/chroot/"${target}"
240
241# Offer to include additional standard repositories for Ubuntu-based chroots.
242alt_repos=
243grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && {
244  while :; do
245    echo "Would you like to add ${distname}-updates and ${distname}-security "
246    printf "to the chroot's sources.list (y/n)? "
247    read alt_repos
248    case "${alt_repos}" in
249      y|Y)
250        alt_repos="y"
251        break
252      ;;
253      n|N)
254        break
255      ;;
256    esac
257  done
258  echo
259}
260
261# Check for non-standard file system mount points and ask the user whether
262# they should be imported into the chroot environment
263# We limit to the first 26 mount points that much some basic heuristics,
264# because a) that allows us to enumerate choices with a single character,
265# and b) if we find more than 26 mount points, then these are probably
266# false-positives and something is very unusual about the system's
267# configuration. No need to spam the user with even more information that
268# is likely completely irrelevant.
269if [ -z "${bind_mounts}" ]; then
270  mounts="$(awk '$2 != "/" && $2 !~ "^/boot" && $2 !~ "^/home" &&
271                 $2 !~ "^/media" && $2 !~ "^/run" &&
272                 ($3 ~ "ext[2-4]" || $3 == "reiserfs" || $3 == "btrfs" ||
273                 $3 == "xfs" || $3 == "jfs" || $3 == "u?msdos" ||
274                 $3 == "v?fat" || $3 == "hfs" || $3 == "ntfs" ||
275                 $3 ~ "nfs[4-9]?" || $3 == "smbfs" || $3 == "cifs") {
276                   print $2
277                 }' /proc/mounts |
278            head -n26)"
279  if [ -n "${mounts}" ]; then
280    echo "You appear to have non-standard mount points that you"
281    echo "might want to import into the chroot environment:"
282    echo
283    sel=
284    while :; do
285      # Print a menu, listing all non-default mounts of local or network
286      # file systems.
287      j=1; for m in ${mounts}; do
288        c="$(printf $(printf '\\%03o' $((64+$j))))"
289        echo "$sel" | grep -qs $c &&
290          state="mounted in chroot" || state="$(tput el)"
291        printf "   $c) %-40s${state}\n" "$m"
292        j=$(($j+1))
293      done
294      # Allow user to interactively (de-)select any of the entries
295      echo
296      printf "Select mount points that you want to be included or press %s" \
297             "SPACE to continue"
298      c="$(getkey | tr a-z A-Z)"
299      [ "$c" == " " ] && { echo; echo; break; }
300      if [ -z "$c" ] ||
301         [ "$c" '<' 'A' -o $(ord "$c") -gt $((64 + $(ord "$j"))) ]; then
302          # Invalid input, ring the console bell
303          tput bel
304      else
305        # Toggle the selection for the given entry
306        if echo "$sel" | grep -qs $c; then
307          sel="$(printf "$sel" | sed "s/$c//")"
308        else
309          sel="$sel$c"
310        fi
311      fi
312      # Reposition cursor to the top of the list of entries
313      tput cuu $(($j + 1))
314      echo
315    done
316  fi
317  j=1; for m in ${mounts}; do
318    c="$(chr $(($j + 64)))"
319    if echo "$sel" | grep -qs $c; then
320      bind_mounts="${bind_mounts}$m $m none rw,bind 0 0
321"
322    fi
323    j=$(($j+1))
324  done
325fi
326
327# Remove stale entry from /etc/schroot/schroot.conf. Entries start
328# with the target name in square brackets, followed by an arbitrary
329# number of lines. The entry stops when either the end of file has
330# been reached, or when the beginning of a new target is encountered.
331# This means, we cannot easily match for a range of lines in
332# "sed". Instead, we actually have to iterate over each line and check
333# whether it is the beginning of a new entry.
334sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p'       \
335         /etc/schroot/schroot.conf
336
337# Download base system. This takes some time
338if [ -z "${mirror}" ]; then
339 grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
340   mirror="http://archive.ubuntu.com/ubuntu" ||
341   mirror="http://ftp.us.debian.org/debian"
342fi
343
344sudo ${http_proxy:+http_proxy="${http_proxy}"} debootstrap ${archflag} \
345    "${distname}" "/var/lib/chroot/${target}"  "$mirror"
346
347# Add new entry to /etc/schroot/schroot.conf
348grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
349  brand="Ubuntu" || brand="Debian"
350if [ -z "${chroot_groups}" ]; then
351  chroot_groups="${admin},$(id -gn)"
352fi
353
354if [ -d '/etc/schroot/default' ]; then
355  new_version=1
356  fstab="/etc/schroot/${target}/fstab"
357else
358  new_version=0
359  fstab="/etc/schroot/mount-${target}"
360fi
361
362if [ "$new_version" = "1" ]; then
363  sudo cp -ar /etc/schroot/default /etc/schroot/${target}
364
365  sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
366[${target%bit}]
367description=${brand} ${distname} ${arch}
368type=directory
369directory=/var/lib/chroot/${target}
370users=root
371groups=${chroot_groups}
372root-groups=${chroot_groups}
373personality=linux$([ "${arch}" != 64bit ] && echo 32)
374profile=${target}
375
376EOF
377  [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] &&
378    printf "${bind_mounts}" |
379      sudo sh -c "cat >>${fstab}"
380else
381  # Older versions of schroot wanted a "priority=" line, whereas recent
382  # versions deprecate "priority=" and warn if they see it. We don't have
383  # a good feature test, but scanning for the string "priority=" in the
384  # existing "schroot.conf" file is a good indication of what to do.
385  priority=$(grep -qs 'priority=' /etc/schroot/schroot.conf &&
386           echo 'priority=3' || :)
387  sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
388[${target%bit}]
389description=${brand} ${distname} ${arch}
390type=directory
391directory=/var/lib/chroot/${target}
392users=root
393groups=${chroot_groups}
394root-groups=${chroot_groups}
395personality=linux$([ "${arch}" != 64bit ] && echo 32)
396script-config=script-${target}
397${priority}
398
399EOF
400
401  # Set up a list of mount points that is specific to this
402  # chroot environment.
403  sed '/^FSTAB=/s,"[^"]*","'"${fstab}"'",' \
404           /etc/schroot/script-defaults |
405    sudo sh -c 'cat >/etc/schroot/script-'"${target}"
406  sed '\,^/home[/[:space:]],s/\([,[:space:]]\)bind[[:space:]]/\1rbind /' \
407    /etc/schroot/mount-defaults |
408    sudo sh -c "cat > ${fstab}"
409fi
410
411# Add the extra mount points that the user told us about
412[ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] &&
413  printf "${bind_mounts}" |
414    sudo sh -c 'cat >>'"${fstab}"
415
416# If this system has a "/media" mountpoint, import it into the chroot
417# environment. Most modern distributions use this mount point to
418# automatically mount devices such as CDROMs, USB sticks, etc...
419if [ -d /media ] &&
420   ! grep -qs '^/media' "${fstab}"; then
421  echo '/media /media none rw,rbind 0 0' |
422    sudo sh -c 'cat >>'"${fstab}"
423fi
424
425# Share /dev/shm, /run and /run/shm.
426grep -qs '^/dev/shm' "${fstab}" ||
427  echo '/dev/shm /dev/shm none rw,bind 0 0' |
428    sudo sh -c 'cat >>'"${fstab}"
429if [ ! -d "/var/lib/chroot/${target}/run" ] &&
430   ! grep -qs '^/run' "${fstab}"; then
431  echo '/run /run none rw,bind 0 0' |
432    sudo sh -c 'cat >>'"${fstab}"
433fi
434if ! grep -qs '^/run/shm' "${fstab}"; then
435  { [ -d /run ] && echo '/run/shm /run/shm none rw,bind 0 0' ||
436                   echo '/dev/shm /run/shm none rw,bind 0 0'; } |
437    sudo sh -c 'cat >>'"${fstab}"
438fi
439
440# Set up a special directory that changes contents depending on the target
441# that is executing.
442d="$(readlink -f "${HOME}/chroot" 2>/dev/null || echo "${HOME}/chroot")"
443s="${d}/.${target}"
444echo "${s} ${d} none rw,bind 0 0" |
445  sudo sh -c 'cat >>'"${target}"
446mkdir -p "${s}"
447
448# Install a helper script to launch commands in the chroot
449sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<'EOF'
450#!/bin/bash
451
452chroot="${0##*/}"
453
454wrap() {
455  # Word-wrap the text passed-in on stdin. Optionally, on continuation lines
456  # insert the same number of spaces as the number of characters in the
457  # parameter(s) passed to this function.
458  # If the "fold" program cannot be found, or if the actual width of the
459  # terminal cannot be determined, this function doesn't attempt to do any
460  # wrapping.
461  local f="$(type -P fold)"
462  [ -z "${f}" ] && { cat; return; }
463  local c="$(stty -a </dev/tty 2>/dev/null |
464             sed 's/.*columns[[:space:]]*\([0-9]*\).*/\1/;t;d')"
465  [ -z "${c}" ] && { cat; return; }
466  local i="$(echo "$*"|sed 's/./ /g')"
467  local j="$(printf %s "${i}"|wc -c)"
468  if [ "${c}" -gt "${j}" ]; then
469    dd bs=1 count="${j}" 2>/dev/null
470    "${f}" -sw "$((${c}-${j}))" | sed '2,$s/^/'"${i}"'/'
471  else
472    "${f}" -sw "${c}"
473  fi
474}
475
476help() {
477  echo "Usage ${0##*/} [-h|--help] [-c|--clean] [-C|--clean-all] [-l|--list] [--] args" | wrap "Usage ${0##*/} "
478  echo "  help:      print this message"                                                | wrap "             "
479  echo "  list:      list all known chroot environments"                                | wrap "             "
480  echo "  clean:     remove all old chroot sessions for \"${chroot}\""                  | wrap "             "
481  echo "  clean-all: remove all old chroot sessions for all environments"               | wrap "             "
482  exit 0
483}
484
485clean() {
486  local s t rc
487  rc=0
488  for s in $(schroot -l --all-sessions); do
489    if [ -n "$1" ]; then
490      t="${s#session:}"
491      [ "${t#${chroot}-}" == "${t}" ] && continue
492    fi
493    if ls -l /proc/*/{cwd,fd} 2>/dev/null |
494       fgrep -qs "/var/lib/schroot/mount/${t}"; then
495      echo "Session \"${t}\" still has active users, not cleaning up" | wrap
496      rc=1
497      continue
498    fi
499    sudo schroot -c "${s}" -e || rc=1
500  done
501  exit ${rc}
502}
503
504list() {
505  for e in $(schroot -l); do
506    e="${e#chroot:}"
507    [ -x "/usr/local/bin/${e}" ] || continue
508    if schroot -l --all-sessions 2>/dev/null |
509       sed 's/^session://' |
510       grep -qs "^${e}-"; then
511      echo "${e} is currently active"
512    else
513      echo "${e}"
514    fi
515  done
516  exit 0
517}
518
519while [ "$#" -ne 0 ]; do
520  case "$1" in
521    --)             shift; break;;
522    -h|--help)      shift; help;;
523    -l|--list)      shift; list;;
524    -c|--clean)     shift; clean "${chroot}";;
525    -C|--clean-all) shift; clean;;
526    *)              break;;
527  esac
528done
529
530# Start a new chroot session and keep track of the session id. We inject this
531# id into all processes that run inside the chroot. Unless they go out of their
532# way to clear their environment, we can then later identify our child and
533# grand-child processes by scanning their environment.
534session="$(schroot -c "${chroot}" -b)"
535export CHROOT_SESSION_ID="${session}"
536
537# Set GOMA_TMP_DIR for better handling of goma inside chroot.
538export GOMA_TMP_DIR="/tmp/goma_tmp_$CHROOT_SESSION_ID"
539mkdir -p "$GOMA_TMP_DIR"
540
541if [ $# -eq 0 ]; then
542  # Run an interactive shell session
543  schroot -c "${session}" -r -p
544else
545  # Run a command inside of the chroot environment
546  p="$1"; shift
547  schroot -c "${session}" -r -p "$p" -- "$@"
548fi
549rc=$?
550
551# Compute the inode of the root directory inside of the chroot environment.
552i=$(schroot -c "${session}" -r -p ls -- -id /proc/self/root/. |
553     awk '{ print $1 }') 2>/dev/null
554other_pids=
555while [ -n "$i" ]; do
556  # Identify processes by the inode number of their root directory. Then
557  # remove all processes that we know belong to other sessions. We use
558  # "sort | uniq -u" to do what amounts to a "set substraction operation".
559  pids=$({ ls -id1 /proc/*/root/. 2>/dev/null |
560         sed -e 's,^[^0-9]*'$i'.*/\([1-9][0-9]*\)/.*$,\1,
561                 t
562                 d';
563         echo "${other_pids}";
564         echo "${other_pids}"; } | sort | uniq -u) >/dev/null 2>&1
565  # Kill all processes that are still left running in the session. This is
566  # typically an assortment of daemon processes that were started
567  # automatically. They result in us being unable to tear down the session
568  # cleanly.
569  [ -z "${pids}" ] && break
570  for j in $pids; do
571    # Unfortunately, the way that schroot sets up sessions has the
572    # side-effect of being unable to tell one session apart from another.
573    # This can result in us attempting to kill processes in other sessions.
574    # We make a best-effort to avoid doing so.
575    k="$( ( xargs -0 -n1 </proc/$j/environ ) 2>/dev/null |
576         sed 's/^CHROOT_SESSION_ID=/x/;t1;d;:1;q')"
577    if [ -n "${k}" -a "${k#x}" != "${session}" ]; then
578      other_pids="${other_pids}
579${j}"
580      continue
581    fi
582    kill -9 $pids
583  done
584done
585# End the chroot session. This should clean up all temporary files. But if we
586# earlier failed to terminate all (daemon) processes inside of the session,
587# deleting the session could fail. When that happens, the user has to manually
588# clean up the stale files by invoking us with "--clean" after having killed
589# all running processes.
590schroot -c "${session}" -e
591# Since no goma processes are running, we can remove goma directory.
592rm -rf "$GOMA_TMP_DIR"
593exit $rc
594EOF
595sudo chown root:root /usr/local/bin/"${target%bit}"
596sudo chmod 755 /usr/local/bin/"${target%bit}"
597
598# Add the standard Ubuntu update repositories if requested.
599[ "${alt_repos}" = "y" -a \
600  -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
601sudo sed -i '/^deb .* [^ -]\+ main$/p
602             s/^\(deb .* [^ -]\+\) main/\1-security main/
603             p
604             t1
605             d
606             :1;s/-security main/-updates main/
607             t
608             d' "/var/lib/chroot/${target}/etc/apt/sources.list"
609
610# Add a few more repositories to the chroot
611[ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
612sudo sed -i 's/ main$/ main restricted universe multiverse/' \
613         "/var/lib/chroot/${target}/etc/apt/sources.list"
614
615# Add the Ubuntu "partner" repository, if available
616if [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
617   HEAD "http://archive.canonical.com/ubuntu/dists/${distname}/partner" \
618   >&/dev/null; then
619  sudo sh -c '
620    echo "deb http://archive.canonical.com/ubuntu" \
621         "'"${distname}"' partner" \
622      >>"/var/lib/chroot/'"${target}"'/etc/apt/sources.list"'
623fi
624
625# Add source repositories, if the user requested we do so
626[ "${add_srcs}" = "y" -a \
627  -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
628sudo sed -i '/^deb[^-]/p
629             s/^deb\([^-]\)/deb-src\1/' \
630         "/var/lib/chroot/${target}/etc/apt/sources.list"
631
632# Set apt proxy if host has set http_proxy
633if [ -n "${http_proxy}" ]; then
634  sudo sh -c '
635    echo "Acquire::http::proxy \"'"${http_proxy}"'\";" \
636        >>"/var/lib/chroot/'"${target}"'/etc/apt/apt.conf"'
637fi
638
639# Update packages
640sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
641  apt-get update; apt-get -y dist-upgrade' || :
642
643# Install a couple of missing packages
644for i in debian-keyring ubuntu-keyring locales sudo; do
645  [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] ||
646    sudo "/usr/local/bin/${target%bit}" apt-get -y install "$i" || :
647done
648
649# Configure locales
650sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
651  l='"${LANG:-en_US}"'; l="${l%%.*}"
652  [ -r /etc/locale.gen ] &&
653    sed -i "s/^# \($l\)/\1/" /etc/locale.gen
654  locale-gen $LANG en_US en_US.UTF-8' || :
655
656# Enable multi-arch support, if available
657sudo "/usr/local/bin/${target%bit}" dpkg --assert-multi-arch >&/dev/null &&
658  [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && {
659  sudo sed -i 's/ / [arch=amd64,i386] /' \
660              "/var/lib/chroot/${target}/etc/apt/sources.list"
661  [ -d /var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/ ] &&
662  sudo "/usr/local/bin/${target%bit}" dpkg --add-architecture \
663      $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) >&/dev/null ||
664    echo foreign-architecture \
665        $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) |
666      sudo sh -c \
667        "cat >'/var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/multiarch'"
668}
669
670# Configure "sudo" package
671sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
672  egrep -qs '"'^$(id -nu) '"' /etc/sudoers ||
673  echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers'
674
675# Install a few more commonly used packages
676sudo "/usr/local/bin/${target%bit}" apt-get -y install                         \
677  autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool     \
678  lsof strace
679
680# If running a 32bit environment on a 64bit machine, install a few binaries
681# as 64bit. This is only done automatically if the chroot distro is the same as
682# the host, otherwise there might be incompatibilities in build settings or
683# runtime dependencies. The user can force it with the '-c' flag.
684host_distro=$(grep -s DISTRIB_CODENAME /etc/lsb-release | \
685  cut -d "=" -f 2)
686if [ "${copy_64}" = "y" -o \
687    "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \
688    file /bin/bash 2>/dev/null | grep -q x86-64; then
689  readlinepkg=$(sudo "/usr/local/bin/${target%bit}" sh -c \
690    'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1')
691  sudo "/usr/local/bin/${target%bit}" apt-get -y install                       \
692    lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1 lib64stdc++6
693  dep=
694  for i in binutils gdb; do
695    [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
696  done
697  [ -n "$dep" ] && sudo apt-get -y install $dep
698  sudo mkdir -p "/var/lib/chroot/${target}/usr/local/lib/amd64"
699  for i in libbfd libpython; do
700    lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } |
701           grep -s "$i" | awk '{ print $3 }')"
702    if [ -n "$lib" -a -r "$lib" ]; then
703      sudo cp "$lib" "/var/lib/chroot/${target}/usr/local/lib/amd64"
704    fi
705  done
706  for lib in libssl libcrypt; do
707    for path in /usr/lib /usr/lib/x86_64-linux-gnu; do
708      sudo cp $path/$lib* \
709              "/var/lib/chroot/${target}/usr/local/lib/amd64/" >&/dev/null || :
710    done
711  done
712  for i in gdb ld; do
713    sudo cp /usr/bin/$i "/var/lib/chroot/${target}/usr/local/lib/amd64/"
714    sudo sh -c "cat >'/var/lib/chroot/${target}/usr/local/bin/$i'" <<EOF
715#!/bin/sh
716exec /lib64/ld-linux-x86-64.so.2 --library-path /usr/local/lib/amd64 \
717  /usr/local/lib/amd64/$i "\$@"
718EOF
719    sudo chmod 755 "/var/lib/chroot/${target}/usr/local/bin/$i"
720  done
721fi
722
723
724# If the install-build-deps.sh script can be found, offer to run it now
725script="$(dirname $(readlink -f "$0"))/install-build-deps.sh"
726if [ -x "${script}" ]; then
727  while :; do
728    echo
729    echo "If you plan on building Chrome inside of the new chroot environment,"
730    echo "you now have to install the build dependencies. Do you want me to"
731    printf "start the script that does this for you (y/n)? "
732    read install_deps
733    case "${install_deps}" in
734      y|Y)
735        echo
736        # We prefer running the script in-place, but this might not be
737        # possible, if it lives on a network filesystem that denies
738        # access to root.
739        tmp_script=
740        if ! sudo /usr/local/bin/"${target%bit}" \
741            sh -c "[ -x '${script}' ]" >&/dev/null; then
742          tmp_script="/tmp/${script##*/}"
743          cp "${script}" "${tmp_script}"
744        fi
745        # Some distributions automatically start an instance of the system-
746        # wide dbus daemon, cron daemon or of the logging daemon, when
747        # installing the Chrome build depencies. This prevents the chroot
748        # session from being closed.  So, we always try to shut down any running
749        # instance of dbus and rsyslog.
750        sudo /usr/local/bin/"${target%bit}" sh -c "${script};
751              rc=$?;
752              /etc/init.d/cron stop >/dev/null 2>&1 || :;
753              /etc/init.d/rsyslog stop >/dev/null 2>&1 || :;
754              /etc/init.d/dbus stop >/dev/null 2>&1 || :;
755              exit $rc"
756        rc=$?
757        [ -n "${tmp_script}" ] && rm -f "${tmp_script}"
758        [ $rc -ne 0 ] && exit $rc
759        break
760      ;;
761      n|N)
762        break
763      ;;
764    esac
765  done
766  echo
767fi
768
769# Check whether ~/chroot is on a (slow) network file system and offer to
770# relocate it. Also offer relocation, if the user appears to have multiple
771# spindles (as indicated by "${bind_mount}" being non-empty).
772# We only offer this option, if it doesn't look as if a chroot environment
773# is currently active. Otherwise, relocation is unlikely to work and it
774# can be difficult for the user to recover from the failed attempt to relocate
775# the ~/chroot directory.
776# We don't aim to solve this problem for every configuration,
777# but try to help with the common cases. For more advanced configuration
778# options, the user can always manually adjust things.
779mkdir -p "${HOME}/chroot/"
780if [ ! -h "${HOME}/chroot" ] &&
781   ! egrep -qs '^[^[:space:]]*/chroot' /etc/fstab &&
782   { [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] ||
783     is_network_drive "${HOME}/chroot"; } &&
784   ! egrep -qs '/var/lib/[^/]*chroot/.*/chroot' /proc/mounts; then
785  echo "${HOME}/chroot is currently located on the same device as your"
786  echo "home directory."
787  echo "This might not be what you want. Do you want me to move it somewhere"
788  echo "else?"
789  # If the computer has multiple spindles, many users configure all or part of
790  # the secondary hard disk to be writable by the primary user of this machine.
791  # Make some reasonable effort to detect this type of configuration and
792  # then offer a good location for where to put the ~/chroot directory.
793  suggest=
794  for i in $(echo "${bind_mounts}"|cut -d ' ' -f 1); do
795    if [ -d "$i" -a -w "$i" -a \( ! -a "$i/chroot" -o -w "$i/chroot/." \) ] &&
796       ! is_network_drive "$i"; then
797      suggest="$i"
798    else
799      for j in "$i/"*; do
800        if [ -d "$j" -a -w "$j" -a \
801             \( ! -a "$j/chroot" -o -w "$j/chroot/." \) ] &&
802           ! is_network_drive "$j"; then
803          suggest="$j"
804        else
805          for k in "$j/"*; do
806            if [ -d "$k" -a -w "$k" -a \
807                 \( ! -a "$k/chroot" -o -w "$k/chroot/." \) ] &&
808               ! is_network_drive "$k"; then
809              suggest="$k"
810              break
811            fi
812          done
813        fi
814        [ -n "${suggest}" ] && break
815      done
816    fi
817    [ -n "${suggest}" ] && break
818  done
819  def_suggest="${HOME}"
820  if [ -n "${suggest}" ]; then
821    # For home directories that reside on network drives, make our suggestion
822    # the default option. For home directories that reside on a local drive,
823    # require that the user manually enters the new location.
824    if is_network_drive "${HOME}"; then
825      def_suggest="${suggest}"
826    else
827      echo "A good location would probably be in \"${suggest}\""
828    fi
829  fi
830  while :; do
831    printf "Physical location [${def_suggest}]: "
832    read dir
833    [ -z "${dir}" ] && dir="${def_suggest}"
834    [ "${dir%%/}" == "${HOME%%/}" ] && break
835    if ! [ -d "${dir}" -a -w "${dir}" ] ||
836       [ -a "${dir}/chroot" -a ! -w "${dir}/chroot/." ]; then
837      echo "Cannot write to ${dir}/chroot. Please try again"
838    else
839      mv "${HOME}/chroot" "${dir}/chroot"
840      ln -s "${dir}/chroot" "${HOME}/chroot"
841      for i in $(list_all_chroots); do
842        sudo "$i" mkdir -p "${dir}/chroot"
843      done
844      sudo sed -i "s,${HOME}/chroot,${dir}/chroot,g" /etc/schroot/mount-*
845      break
846    fi
847  done
848fi
849
850# Clean up package files
851sudo schroot -c "${target%bit}" -p -- apt-get clean
852sudo apt-get clean
853
854trap '' INT TERM QUIT HUP
855trap '' EXIT
856
857# Let the user know what we did
858cat <<EOF
859
860
861Successfully installed ${distname} ${arch}
862
863You can run programs inside of the chroot by invoking the
864"/usr/local/bin/${target%bit}" command.
865
866This command can be used with arguments, in order to just run a single
867program inside of the chroot environment (e.g. "${target%bit} make chrome")
868or without arguments, in order to run an interactive shell session inside
869of the chroot environment.
870
871If you need to run things as "root", you can use "sudo" (e.g. try
872"sudo ${target%bit} apt-get update").
873
874Your home directory is shared between the host and the chroot. But I
875configured "${HOME}/chroot" to be private to the chroot environment.
876You can use it for files that need to differ between environments. This
877would be a good place to store binaries that you have built from your
878source files.
879
880For Chrome, this probably means you want to make your "out" directory a
881symbolic link that points somewhere inside of "${HOME}/chroot".
882
883You still need to run "gclient runhooks" whenever you switch from building
884outside of the chroot to inside of the chroot. But you will find that you
885don't have to repeatedly erase and then completely rebuild all your object
886and binary files.
887
888EOF
889