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           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